@forthtilliath/expo-release-updates-ui 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -138,24 +138,28 @@ Mount it once near the root: it checks on launch (`useUpdateCheck`), shows the b
138
138
  ```tsx
139
139
  import { UpdateNotifier } from "@forthtilliath/expo-release-updates-ui/UpdateNotifier";
140
140
 
141
+ const repo = { owner: "acme", repo: "app" };
142
+
141
143
  <View
142
144
  style={{ position: "absolute", top: 56, left: 16, right: 16, zIndex: 10 }}
143
145
  >
144
146
  <UpdateNotifier
145
147
  enabled={settings !== undefined}
146
148
  currentVersion={Constants.expoConfig?.version ?? "0.0.0"}
147
- checkForUpdate={fetchLatestRelease}
149
+ checkForUpdate={() => fetchLatestRelease(repo)}
148
150
  getLastCheck={() => ({
149
151
  lastCheckedAt: settings?.lastUpdateCheckAt ?? null,
150
152
  dismissedVersion: settings?.dismissedUpdateVersion ?? null,
151
153
  })}
152
154
  onChecked={(at) => recordUpdateCheck(at)}
153
155
  onDismissVersion={(version) => dismissUpdateVersion(version)}
154
- downloadAndInstallApk={(apkUrl, onProgress) =>
156
+ downloadAndInstallApk={(apkUrl, onProgress, apkSha256) =>
155
157
  downloadAndInstallApk({
156
158
  apkUrl,
157
159
  fileName: "myapp-update.apk",
158
160
  onProgress,
161
+ allowedUrlPrefix: releaseDownloadUrlPrefix(repo),
162
+ expectedSha256: apkSha256,
159
163
  })
160
164
  }
161
165
  styles={{ banner: { container: { backgroundColor: colors.surface } } }}
@@ -165,7 +169,9 @@ import { UpdateNotifier } from "@forthtilliath/expo-release-updates-ui/UpdateNot
165
169
 
166
170
  Pass **`enabled={false}` until the settings `getLastCheck` reads have loaded** (async database, live query…): the check runs once, on mount, so starting earlier would ignore both the 12h throttle and the dismissed version. Positioning is left to the parent. `labels` / `styles` take `{ banner, install }`, same keys as `UpdateAvailableBanner` and `UpdateInstallCard`.
167
171
 
168
- The building blocks are exported too, to compose a different flow: `useApkInstall(downloadAndInstallApk)` (`{ state, start(version, apkUrl), retry, reset }`) and `<UpdateInstallCard state onRetry onClose />` (renders nothing while idle).
172
+ `downloadAndInstallApk` receives the release's `apkSha256` (from `fetchLatestRelease`) as its third argument: pass it on as `expectedSha256` so a swapped or damaged APK is never installed (needs expo-file-system 58+), and restrict the URL to your repo's releases with `allowedUrlPrefix`.
173
+
174
+ The building blocks are exported too, to compose a different flow: `useApkInstall(downloadAndInstallApk)` (`{ state, start(version, apkUrl, apkSha256?), retry, reset }`) and `<UpdateInstallCard state onRetry onClose />` (renders nothing while idle).
169
175
 
170
176
  ## Scripts
171
177
 
@@ -37,7 +37,7 @@ function ActiveUpdateNotifier({ downloadAndInstallApk, onDismissVersion, locale
37
37
  // Not persisted as dismissed: if the install doesn't go through, the
38
38
  // banner may come back on a later launch (unlike "Ignore").
39
39
  update.dismiss();
40
- install.start(release.version, release.apkUrl);
40
+ install.start(release.version, release.apkUrl, release.apkSha256);
41
41
  }, onDismiss: () => {
42
42
  onDismissVersion(release.version);
43
43
  update.dismiss();
@@ -1,5 +1,9 @@
1
- /** Downloads an APK then hands it to Android's installer, reporting progress from 0 to 1. */
2
- export type DownloadAndInstallApk = (apkUrl: string, onProgress?: (fraction: number) => void) => Promise<void>;
1
+ /**
2
+ * Downloads an APK then hands it to Android's installer, reporting progress
3
+ * from 0 to 1. `apkSha256` is the release's `apkSha256` (from
4
+ * `fetchLatestRelease`), to pass on as `expectedSha256`.
5
+ */
6
+ export type DownloadAndInstallApk = (apkUrl: string, onProgress?: (fraction: number) => void, apkSha256?: string) => Promise<void>;
3
7
  export type ApkInstallState = {
4
8
  status: "idle";
5
9
  } | {
@@ -10,10 +14,11 @@ export type ApkInstallState = {
10
14
  status: "error";
11
15
  version: string;
12
16
  apkUrl: string;
17
+ apkSha256?: string;
13
18
  };
14
19
  export declare function useApkInstall(downloadAndInstallApk: DownloadAndInstallApk): {
15
20
  state: ApkInstallState;
16
- start: (version: string, apkUrl: string) => void;
21
+ start: (version: string, apkUrl: string, apkSha256?: string) => void;
17
22
  retry: () => void;
18
23
  reset: () => void;
19
24
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useApkInstall.d.ts","sourceRoot":"","sources":["../src/useApkInstall.ts"],"names":[],"mappings":"AAEA,6FAA6F;AAC7F,MAAM,MAAM,qBAAqB,GAAG,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,KACpC,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAKzD,wBAAgB,aAAa,CAAC,qBAAqB,EAAE,qBAAqB;;qBAGhD,MAAM,UAAU,MAAM;;;EAsB/C"}
1
+ {"version":3,"file":"useApkInstall.d.ts","sourceRoot":"","sources":["../src/useApkInstall.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,EACvC,SAAS,CAAC,EAAE,MAAM,KACf,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC5D;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAK7E,wBAAgB,aAAa,CAAC,qBAAqB,EAAE,qBAAqB;;qBAGhD,MAAM,UAAU,MAAM,cAAc,MAAM;;;EA4BnE"}
@@ -4,21 +4,22 @@ import { useState } from "react";
4
4
  // state (keeping the URL) so the user can retry.
5
5
  export function useApkInstall(downloadAndInstallApk) {
6
6
  const [state, setState] = useState({ status: "idle" });
7
- function start(version, apkUrl) {
7
+ function start(version, apkUrl, apkSha256) {
8
8
  setState({ status: "downloading", version, progress: 0 });
9
9
  downloadAndInstallApk(apkUrl, (progress) => {
10
10
  setState({ status: "downloading", version, progress });
11
- })
11
+ }, apkSha256)
12
12
  .then(() => {
13
13
  setState({ status: "idle" });
14
14
  })
15
15
  .catch(() => {
16
- setState({ status: "error", version, apkUrl });
16
+ setState({ status: "error", version, apkUrl, apkSha256 });
17
17
  });
18
18
  }
19
19
  function retry() {
20
- if (state.status === "error")
21
- start(state.version, state.apkUrl);
20
+ if (state.status === "error") {
21
+ start(state.version, state.apkUrl, state.apkSha256);
22
+ }
22
23
  }
23
24
  function reset() {
24
25
  setState({ status: "idle" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forthtilliath/expo-release-updates-ui",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",
@@ -22,7 +22,7 @@
22
22
  "./*": "./dist/*.js"
23
23
  },
24
24
  "dependencies": {
25
- "@forthtilliath/expo-release-updates": "^0.6.1"
25
+ "@forthtilliath/expo-release-updates": "^0.8.0"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=19",