@capuchoo/updater 0.1.0 → 0.1.1
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/dist/index.d.ts +77 -35
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/{ota.service-t2_hIxcy.js → ota.service-tv6eNQcB.js} +75 -18
- package/dist/ota.service-tv6eNQcB.js.map +1 -0
- package/dist/vue.js +1 -1
- package/package.json +6 -2
- package/dist/ota.service-t2_hIxcy.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
import { a as DownloadProgress, i as notifyAppReady, n as discardBundle, o as cleanApkCache, r as getCurrentBundle, s as downloadNativeUpdate, t as applyOtaUpdate } from "./ota.service-B3LrcCPr.js";
|
|
2
|
-
import { Environment, Platform, ResolvedUpdate, ResolvedUpdate as ResolvedUpdate$1, UpdateCheckResponse, UpdateCheckResponse as UpdateCheckResponse$1, UpdateEvent, UpdateEvent as UpdateEvent$1, UpdateKind } from "@capuchoo/core";
|
|
2
|
+
import { Environment, Platform, ResolvedUpdate, ResolvedUpdate as ResolvedUpdate$1, UpdateCheckRequest, UpdateCheckResponse, UpdateCheckResponse as UpdateCheckResponse$1, UpdateEvent, UpdateEvent as UpdateEvent$1, UpdateKind } from "@capuchoo/core";
|
|
3
|
+
//#region src/device.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Facts about the running build that the server needs in order to decide
|
|
6
|
+
* whether an update applies.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Native build number of the installed binary.
|
|
10
|
+
*
|
|
11
|
+
* Returns 0 off-device. The old implementation returned 999999 on web, which
|
|
12
|
+
* meant a browser session claimed to be newer than every published release and
|
|
13
|
+
* so never saw an update - masking the very bug you would be debugging.
|
|
14
|
+
*/
|
|
15
|
+
declare function getVersionCode(): Promise<number>;
|
|
16
|
+
/**
|
|
17
|
+
* Semantic version of the web bundle currently applied.
|
|
18
|
+
*
|
|
19
|
+
* `"builtin"` means no OTA bundle has been applied yet and the app is running
|
|
20
|
+
* the assets compiled into the binary. The server treats it as 0.0.0, so it
|
|
21
|
+
* must be reported honestly rather than sent as a constant.
|
|
22
|
+
*/
|
|
23
|
+
declare function getBundleVersion(): Promise<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Stable per-install identifier, supplied by the OTA plugin.
|
|
26
|
+
*
|
|
27
|
+
* The plugin persists this natively. Reading `localStorage.device_id` instead -
|
|
28
|
+
* as the app template did - returns null on a fresh install and is wiped
|
|
29
|
+
* whenever the WebView data is cleared, so channel overrides and per-device
|
|
30
|
+
* stats silently stopped working.
|
|
31
|
+
*/
|
|
32
|
+
declare function getDeviceId(): Promise<string>;
|
|
33
|
+
declare function getPlatform(): "android" | "ios" | "web";
|
|
34
|
+
declare function isNative(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Version of the OTA plugin the app is running.
|
|
37
|
+
*
|
|
38
|
+
* Useful when a device misbehaves: plugin version explains more failures than
|
|
39
|
+
* app version does.
|
|
40
|
+
*/
|
|
41
|
+
declare function getPluginVersion(): Promise<string | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* OS version and emulator flag, from `@capacitor/device`.
|
|
44
|
+
*
|
|
45
|
+
* That package is an *optional* peer: it is a separate install, and an app that
|
|
46
|
+
* does not have it should still be able to check for updates. So it is imported
|
|
47
|
+
* dynamically and every failure - not installed, not registered, throwing on an
|
|
48
|
+
* odd platform - resolves to `undefined`, which the request builder omits.
|
|
49
|
+
*/
|
|
50
|
+
declare function getOsFacts(): Promise<{
|
|
51
|
+
versionOs?: string;
|
|
52
|
+
isEmulator?: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
//#endregion
|
|
3
55
|
//#region src/api.service.d.ts
|
|
4
56
|
/** Raised when the updater is misconfigured, rather than reporting "up to date". */
|
|
5
57
|
declare class UpdaterConfigError extends Error {
|
|
@@ -21,6 +73,29 @@ declare class UpdateCheckBlockedError extends Error {
|
|
|
21
73
|
* `min_update_version` gate, so a device could be told to install an OTA
|
|
22
74
|
* bundle its binary was too old to run.
|
|
23
75
|
*/
|
|
76
|
+
interface DeviceFacts {
|
|
77
|
+
appId: string;
|
|
78
|
+
platform: ReturnType<typeof getPlatform>;
|
|
79
|
+
channel?: string | undefined;
|
|
80
|
+
isProd: boolean;
|
|
81
|
+
versionCode: number;
|
|
82
|
+
versionName: string;
|
|
83
|
+
deviceId: string;
|
|
84
|
+
pluginVersion?: string | undefined;
|
|
85
|
+
versionOs?: string | undefined;
|
|
86
|
+
isEmulator?: boolean | undefined;
|
|
87
|
+
customId?: string | undefined;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Assemble the check payload.
|
|
91
|
+
*
|
|
92
|
+
* Pure, and separate from the plugin calls that gather the facts, so the shape
|
|
93
|
+
* of what goes on the wire can be tested without a device. Fields the app could
|
|
94
|
+
* not determine are *omitted* rather than sent empty: the server writes only the
|
|
95
|
+
* keys it receives, so a placeholder would overwrite a better value that an
|
|
96
|
+
* earlier, better-informed check had already stored.
|
|
97
|
+
*/
|
|
98
|
+
declare function buildCheckRequest(facts: DeviceFacts): UpdateCheckRequest;
|
|
24
99
|
declare function checkForUpdate(): Promise<ResolvedUpdate$1 | null>;
|
|
25
100
|
/**
|
|
26
101
|
* Records a native update lifecycle event.
|
|
@@ -73,39 +148,6 @@ declare function getUpdaterConfig(): UpdaterConfig;
|
|
|
73
148
|
*/
|
|
74
149
|
declare function describeConfigProblems(config: UpdaterConfig): string[];
|
|
75
150
|
//#endregion
|
|
76
|
-
//#region src/device.d.ts
|
|
77
|
-
/**
|
|
78
|
-
* Facts about the running build that the server needs in order to decide
|
|
79
|
-
* whether an update applies.
|
|
80
|
-
*/
|
|
81
|
-
/**
|
|
82
|
-
* Native build number of the installed binary.
|
|
83
|
-
*
|
|
84
|
-
* Returns 0 off-device. The old implementation returned 999999 on web, which
|
|
85
|
-
* meant a browser session claimed to be newer than every published release and
|
|
86
|
-
* so never saw an update - masking the very bug you would be debugging.
|
|
87
|
-
*/
|
|
88
|
-
declare function getVersionCode(): Promise<number>;
|
|
89
|
-
/**
|
|
90
|
-
* Semantic version of the web bundle currently applied.
|
|
91
|
-
*
|
|
92
|
-
* `"builtin"` means no OTA bundle has been applied yet and the app is running
|
|
93
|
-
* the assets compiled into the binary. The server treats it as 0.0.0, so it
|
|
94
|
-
* must be reported honestly rather than sent as a constant.
|
|
95
|
-
*/
|
|
96
|
-
declare function getBundleVersion(): Promise<string>;
|
|
97
|
-
/**
|
|
98
|
-
* Stable per-install identifier, supplied by the OTA plugin.
|
|
99
|
-
*
|
|
100
|
-
* The plugin persists this natively. Reading `localStorage.device_id` instead -
|
|
101
|
-
* as the app template did - returns null on a fresh install and is wiped
|
|
102
|
-
* whenever the WebView data is cleared, so channel overrides and per-device
|
|
103
|
-
* stats silently stopped working.
|
|
104
|
-
*/
|
|
105
|
-
declare function getDeviceId(): Promise<string>;
|
|
106
|
-
declare function getPlatform(): "android" | "ios" | "web";
|
|
107
|
-
declare function isNative(): boolean;
|
|
108
|
-
//#endregion
|
|
109
151
|
//#region src/install.service.d.ts
|
|
110
152
|
/**
|
|
111
153
|
* Hands a downloaded APK to the Android package installer.
|
|
@@ -117,5 +159,5 @@ declare function isNative(): boolean;
|
|
|
117
159
|
*/
|
|
118
160
|
declare function openNativeInstaller(path: string): Promise<void>;
|
|
119
161
|
//#endregion
|
|
120
|
-
export { type DownloadProgress, type Environment, type Platform, type ResolvedUpdate, UpdateCheckBlockedError, type UpdateCheckResponse, type UpdateEvent, type UpdateKind, type UpdaterConfig, UpdaterConfigError, applyOtaUpdate, checkForUpdate, cleanApkCache, configureUpdater, describeConfigProblems, discardBundle, downloadNativeUpdate, getBundleVersion, getCurrentBundle, getDeviceId, getPlatform, getUpdaterConfig, getVersionCode, isNative, logUpdateEvent, notifyAppReady, openNativeInstaller };
|
|
162
|
+
export { type DeviceFacts, type DownloadProgress, type Environment, type Platform, type ResolvedUpdate, UpdateCheckBlockedError, type UpdateCheckResponse, type UpdateEvent, type UpdateKind, type UpdaterConfig, UpdaterConfigError, applyOtaUpdate, buildCheckRequest, checkForUpdate, cleanApkCache, configureUpdater, describeConfigProblems, discardBundle, downloadNativeUpdate, getBundleVersion, getCurrentBundle, getDeviceId, getOsFacts, getPlatform, getPluginVersion, getUpdaterConfig, getVersionCode, isNative, logUpdateEvent, notifyAppReady, openNativeInstaller };
|
|
121
163
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/device.ts","../src/api.service.ts","../src/config.ts","../src/install.service.ts"],"mappings":";;;;;;;;;;;;;;iBAgBsB,kBAAkB;;;;;;;;iBAkBlB,oBAAoB;;;;;;;;;iBAmBpB,eAAe;iBAWrB;iBAIA;;;;;;;iBAUM,oBAAoB;;;;;;;;;iBAmBpB,cAAc;EAAU;EAAoB;;;;;cC5ErD,2BAA2B;WAC7B;EAEG,YAAA;;;cAQD,gCAAgC;WAClC,UAAU;EAEP,YAAA,UAAU;;;;;;;;;;;;UAuCP;EACf;EACA,UAAU,kBAAkB;EAC5B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;;;;;;iBAYc,kBAAkB,OAAO,cAAc;iBAwBjC,kBAAkB,QAAQ;;;;;;;;iBAgD1B,eACpB,OAAO,eACP,QAAQ,kBACR;EAAY;IACX;;;;;;;;;;;UCrKc;;EAEf;;EAEA;;EAEA;;EAEA;;EAEA;;EAEA;;;;;;iBAoBc,iBAAiB,MAAM,QAAQ;iBAS/B,oBAAoB;;;;;;;;;;iBAuBpB,uBAAuB,QAAQ;;;;;;;;;;;iBC1DzB,oBAAoB,eAAe"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as
|
|
2
|
-
export { UpdateCheckBlockedError, UpdaterConfigError, applyOtaUpdate, checkForUpdate, cleanApkCache, configureUpdater, describeConfigProblems, discardBundle, downloadNativeUpdate, getBundleVersion, getCurrentBundle, getDeviceId, getPlatform, getUpdaterConfig, getVersionCode, isNative, logUpdateEvent, notifyAppReady, openNativeInstaller };
|
|
1
|
+
import { S as getUpdaterConfig, _ as getPluginVersion, a as openNativeInstaller, b as configureUpdater, c as UpdateCheckBlockedError, d as checkForUpdate, f as logUpdateEvent, g as getPlatform, h as getOsFacts, i as notifyAppReady, l as UpdaterConfigError, m as getDeviceId, n as discardBundle, o as cleanApkCache, p as getBundleVersion, r as getCurrentBundle, s as downloadNativeUpdate, t as applyOtaUpdate, u as buildCheckRequest, v as getVersionCode, x as describeConfigProblems, y as isNative } from "./ota.service-tv6eNQcB.js";
|
|
2
|
+
export { UpdateCheckBlockedError, UpdaterConfigError, applyOtaUpdate, buildCheckRequest, checkForUpdate, cleanApkCache, configureUpdater, describeConfigProblems, discardBundle, downloadNativeUpdate, getBundleVersion, getCurrentBundle, getDeviceId, getOsFacts, getPlatform, getPluginVersion, getUpdaterConfig, getVersionCode, isNative, logUpdateEvent, notifyAppReady, openNativeInstaller };
|
|
@@ -109,6 +109,42 @@ function getPlatform() {
|
|
|
109
109
|
function isNative() {
|
|
110
110
|
return Capacitor.isNativePlatform();
|
|
111
111
|
}
|
|
112
|
+
/**
|
|
113
|
+
* Version of the OTA plugin the app is running.
|
|
114
|
+
*
|
|
115
|
+
* Useful when a device misbehaves: plugin version explains more failures than
|
|
116
|
+
* app version does.
|
|
117
|
+
*/
|
|
118
|
+
async function getPluginVersion() {
|
|
119
|
+
if (!Capacitor.isNativePlatform()) return void 0;
|
|
120
|
+
try {
|
|
121
|
+
const { version } = await CapacitorUpdater.getPluginVersion();
|
|
122
|
+
return version || void 0;
|
|
123
|
+
} catch {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* OS version and emulator flag, from `@capacitor/device`.
|
|
129
|
+
*
|
|
130
|
+
* That package is an *optional* peer: it is a separate install, and an app that
|
|
131
|
+
* does not have it should still be able to check for updates. So it is imported
|
|
132
|
+
* dynamically and every failure - not installed, not registered, throwing on an
|
|
133
|
+
* odd platform - resolves to `undefined`, which the request builder omits.
|
|
134
|
+
*/
|
|
135
|
+
async function getOsFacts() {
|
|
136
|
+
if (!Capacitor.isNativePlatform()) return {};
|
|
137
|
+
try {
|
|
138
|
+
const { Device } = await import("@capacitor/device");
|
|
139
|
+
const info = await Device.getInfo();
|
|
140
|
+
return {
|
|
141
|
+
...info.osVersion ? { versionOs: info.osVersion } : {},
|
|
142
|
+
...typeof info.isVirtual === "boolean" ? { isEmulator: info.isVirtual } : {}
|
|
143
|
+
};
|
|
144
|
+
} catch {
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
112
148
|
//#endregion
|
|
113
149
|
//#region src/api.service.ts
|
|
114
150
|
/** Raised when the updater is misconfigured, rather than reporting "up to date". */
|
|
@@ -146,36 +182,57 @@ async function postJson(url, body, timeoutMs) {
|
|
|
146
182
|
}
|
|
147
183
|
}
|
|
148
184
|
/**
|
|
149
|
-
*
|
|
185
|
+
* Assemble the check payload.
|
|
150
186
|
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
* bundle its binary was too old to run.
|
|
187
|
+
* Pure, and separate from the plugin calls that gather the facts, so the shape
|
|
188
|
+
* of what goes on the wire can be tested without a device. Fields the app could
|
|
189
|
+
* not determine are *omitted* rather than sent empty: the server writes only the
|
|
190
|
+
* keys it receives, so a placeholder would overwrite a better value that an
|
|
191
|
+
* earlier, better-informed check had already stored.
|
|
157
192
|
*/
|
|
193
|
+
function buildCheckRequest(facts) {
|
|
194
|
+
const request = {
|
|
195
|
+
appId: facts.appId,
|
|
196
|
+
platform: facts.platform,
|
|
197
|
+
versionCode: String(facts.versionCode),
|
|
198
|
+
versionBuild: String(facts.versionCode),
|
|
199
|
+
version_name: facts.versionName,
|
|
200
|
+
deviceId: facts.deviceId,
|
|
201
|
+
isProd: facts.isProd
|
|
202
|
+
};
|
|
203
|
+
if (facts.channel) {
|
|
204
|
+
request.channel = facts.channel;
|
|
205
|
+
request.defaultChannel = facts.channel;
|
|
206
|
+
}
|
|
207
|
+
if (facts.pluginVersion) request.pluginVersion = facts.pluginVersion;
|
|
208
|
+
if (facts.versionOs) request.versionOs = facts.versionOs;
|
|
209
|
+
if (typeof facts.isEmulator === "boolean") request.isEmulator = facts.isEmulator;
|
|
210
|
+
if (facts.customId) request.customId = facts.customId;
|
|
211
|
+
return request;
|
|
212
|
+
}
|
|
158
213
|
async function checkForUpdate() {
|
|
159
214
|
if (!isNative()) return null;
|
|
160
215
|
const config = getUpdaterConfig();
|
|
161
216
|
const problems = describeConfigProblems(config);
|
|
162
217
|
if (problems.length > 0) throw new UpdaterConfigError(problems);
|
|
163
|
-
const [versionCode, versionName, deviceId] = await Promise.all([
|
|
218
|
+
const [versionCode, versionName, deviceId, pluginVersion, osFacts] = await Promise.all([
|
|
164
219
|
getVersionCode(),
|
|
165
220
|
getBundleVersion(),
|
|
166
|
-
getDeviceId()
|
|
221
|
+
getDeviceId(),
|
|
222
|
+
getPluginVersion(),
|
|
223
|
+
getOsFacts()
|
|
167
224
|
]);
|
|
168
|
-
const request = {
|
|
225
|
+
const request = buildCheckRequest({
|
|
169
226
|
appId: config.appId,
|
|
170
227
|
platform: getPlatform(),
|
|
171
228
|
channel: config.channel,
|
|
172
|
-
|
|
173
|
-
versionCode
|
|
174
|
-
|
|
175
|
-
version_name: versionName,
|
|
229
|
+
isProd: config.environment === "prod",
|
|
230
|
+
versionCode,
|
|
231
|
+
versionName,
|
|
176
232
|
deviceId,
|
|
177
|
-
|
|
178
|
-
|
|
233
|
+
pluginVersion,
|
|
234
|
+
...osFacts
|
|
235
|
+
});
|
|
179
236
|
const response = await postJson(`${config.apiUrl}/api/update`, request, config.timeoutMs);
|
|
180
237
|
if (isBlockingResponse(response)) throw new UpdateCheckBlockedError(response);
|
|
181
238
|
return resolveUpdate(response);
|
|
@@ -365,6 +422,6 @@ async function discardBundle(bundleId) {
|
|
|
365
422
|
}
|
|
366
423
|
}
|
|
367
424
|
//#endregion
|
|
368
|
-
export {
|
|
425
|
+
export { getUpdaterConfig as S, getPluginVersion as _, openNativeInstaller as a, configureUpdater as b, UpdateCheckBlockedError as c, checkForUpdate as d, logUpdateEvent as f, getPlatform as g, getOsFacts as h, notifyAppReady as i, UpdaterConfigError as l, getDeviceId as m, discardBundle as n, cleanApkCache as o, getBundleVersion as p, getCurrentBundle as r, downloadNativeUpdate as s, applyOtaUpdate as t, buildCheckRequest as u, getVersionCode as v, describeConfigProblems as x, isNative as y };
|
|
369
426
|
|
|
370
|
-
//# sourceMappingURL=ota.service-
|
|
427
|
+
//# sourceMappingURL=ota.service-tv6eNQcB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ota.service-tv6eNQcB.js","names":[],"sources":["../src/config.ts","../src/device.ts","../src/api.service.ts","../src/download.service.ts","../src/install.service.ts","../src/ota.service.ts"],"sourcesContent":["/**\n * Runtime configuration for the updater.\n *\n * Values come from the build's `VITE_*` variables, which the CLI injects from\n * the flavour's env file. `configureUpdater` lets an app override any of them\n * at startup - useful for tests and for apps that resolve their endpoint from\n * a login response rather than at build time.\n */\nexport interface UpdaterConfig {\n /** Base URL of the Capucho backend, with no trailing slash. */\n apiUrl: string;\n /** Bundle identifier of this build. Must match what the CLI published. */\n appId: string;\n /** Human-readable name, used in prompts and in the APK cache file name. */\n appName: string;\n /** Channel to consult. Bound to an environment server-side. */\n channel: string;\n /** Free-form: an app may use flavours beyond dev/staging/prod. */\n environment: string;\n /** Milliseconds before an update check is abandoned. */\n timeoutMs: number;\n}\n\nfunction env(key: string): string | undefined {\n // `import.meta.env` is replaced at build time by Vite. Guard the access so\n // the module can also be imported from Node (tests, SSR) without throwing.\n const source = (import.meta as ImportMeta & { env?: Record<string, string | undefined> }).env;\n return source?.[key];\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nlet overrides: Partial<UpdaterConfig> = {};\n\n/**\n * Overrides configuration resolved from the build. Call before `init`.\n * Passing `{}` clears previous overrides.\n */\nexport function configureUpdater(next: Partial<UpdaterConfig>): void {\n overrides = { ...overrides, ...next };\n}\n\n/** @internal exposed for tests. */\nexport function resetUpdaterConfig(): void {\n overrides = {};\n}\n\nexport function getUpdaterConfig(): UpdaterConfig {\n const apiUrl = overrides.apiUrl ?? env(\"VITE_UPDATE_API_URL\") ?? \"\";\n\n return {\n apiUrl: stripTrailingSlash(apiUrl),\n appId: overrides.appId ?? env(\"VITE_APP_ID\") ?? \"\",\n appName: overrides.appName ?? env(\"VITE_APP_NAME\") ?? \"the app\",\n channel: overrides.channel ?? env(\"VITE_UPDATE_CHANNEL\") ?? \"prod\",\n environment:\n overrides.environment ?? env(\"VITE_ENVIRONMENT\") ?? (env(\"PROD\") === \"true\" ? \"prod\" : \"dev\"),\n timeoutMs: overrides.timeoutMs ?? 30_000,\n };\n}\n\n/**\n * Reasons the updater cannot run, as user-facing strings.\n *\n * The previous implementation defaulted `apiUrl` to a hard-coded Render URL and\n * `appId` to a hard-coded bundle id. A build with a missing variable therefore\n * silently pointed at somebody else's backend, or asked for the wrong app, and\n * reported \"you are up to date\". Failing loudly is the whole point of this\n * function.\n */\nexport function describeConfigProblems(config: UpdaterConfig): string[] {\n const problems: string[] = [];\n if (!config.apiUrl) {\n problems.push(\"VITE_UPDATE_API_URL is not set, so updates cannot be checked\");\n }\n if (!config.appId) {\n problems.push(\"VITE_APP_ID is not set, so the server cannot identify this build\");\n }\n return problems;\n}\n","import { App } from \"@capacitor/app\";\nimport { Capacitor } from \"@capacitor/core\";\nimport { CapacitorUpdater } from \"@capgo/capacitor-updater\";\n\n/**\n * Facts about the running build that the server needs in order to decide\n * whether an update applies.\n */\n\n/**\n * Native build number of the installed binary.\n *\n * Returns 0 off-device. The old implementation returned 999999 on web, which\n * meant a browser session claimed to be newer than every published release and\n * so never saw an update - masking the very bug you would be debugging.\n */\nexport async function getVersionCode(): Promise<number> {\n if (!Capacitor.isNativePlatform()) return 0;\n\n try {\n const info = await App.getInfo();\n return Number.parseInt(info.build, 10) || 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * Semantic version of the web bundle currently applied.\n *\n * `\"builtin\"` means no OTA bundle has been applied yet and the app is running\n * the assets compiled into the binary. The server treats it as 0.0.0, so it\n * must be reported honestly rather than sent as a constant.\n */\nexport async function getBundleVersion(): Promise<string> {\n if (!Capacitor.isNativePlatform()) return \"builtin\";\n\n try {\n const current = await CapacitorUpdater.current();\n return current.bundle.version || \"builtin\";\n } catch {\n return \"builtin\";\n }\n}\n\n/**\n * Stable per-install identifier, supplied by the OTA plugin.\n *\n * The plugin persists this natively. Reading `localStorage.device_id` instead -\n * as the app template did - returns null on a fresh install and is wiped\n * whenever the WebView data is cleared, so channel overrides and per-device\n * stats silently stopped working.\n */\nexport async function getDeviceId(): Promise<string> {\n if (!Capacitor.isNativePlatform()) return \"web\";\n\n try {\n const { deviceId } = await CapacitorUpdater.getDeviceId();\n return deviceId || \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nexport function getPlatform(): \"android\" | \"ios\" | \"web\" {\n return Capacitor.getPlatform() as \"android\" | \"ios\" | \"web\";\n}\n\nexport function isNative(): boolean {\n return Capacitor.isNativePlatform();\n}\n\n/**\n * Version of the OTA plugin the app is running.\n *\n * Useful when a device misbehaves: plugin version explains more failures than\n * app version does.\n */\nexport async function getPluginVersion(): Promise<string | undefined> {\n if (!Capacitor.isNativePlatform()) return undefined;\n\n try {\n const { version } = await CapacitorUpdater.getPluginVersion();\n return version || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * OS version and emulator flag, from `@capacitor/device`.\n *\n * That package is an *optional* peer: it is a separate install, and an app that\n * does not have it should still be able to check for updates. So it is imported\n * dynamically and every failure - not installed, not registered, throwing on an\n * odd platform - resolves to `undefined`, which the request builder omits.\n */\nexport async function getOsFacts(): Promise<{ versionOs?: string; isEmulator?: boolean }> {\n if (!Capacitor.isNativePlatform()) return {};\n\n try {\n const { Device } = await import(\"@capacitor/device\");\n const info = await Device.getInfo();\n return {\n ...(info.osVersion ? { versionOs: info.osVersion } : {}),\n ...(typeof info.isVirtual === \"boolean\" ? { isEmulator: info.isVirtual } : {}),\n };\n } catch {\n return {};\n }\n}\n","import {\n isBlockingResponse,\n resolveUpdate,\n type ResolvedUpdate,\n type UpdateCheckRequest,\n type UpdateCheckResponse,\n type UpdateEvent,\n type UpdateEventPayload,\n} from \"@capuchoo/core\";\nimport { getUpdaterConfig, describeConfigProblems } from \"./config.js\";\nimport {\n getBundleVersion,\n getDeviceId,\n getOsFacts,\n getPlatform,\n getPluginVersion,\n getVersionCode,\n isNative,\n} from \"./device.js\";\n\n/** Raised when the updater is misconfigured, rather than reporting \"up to date\". */\nexport class UpdaterConfigError extends Error {\n readonly problems: string[];\n\n constructor(problems: string[]) {\n super(problems.join(\"; \"));\n this.name = \"UpdaterConfigError\";\n this.problems = problems;\n }\n}\n\n/** Raised when the server says the request itself cannot be served. */\nexport class UpdateCheckBlockedError extends Error {\n readonly response: UpdateCheckResponse;\n\n constructor(response: UpdateCheckResponse) {\n super(response.message ?? \"The update service rejected this request\");\n this.name = \"UpdateCheckBlockedError\";\n this.response = response;\n }\n}\n\nasync function postJson<T>(url: string, body: unknown, timeoutMs: number): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`${url} responded ${response.status} ${response.statusText}`);\n }\n\n return (await response.json()) as T;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Asks the server what this device should be running.\n *\n * One request, one endpoint. The app template used to call\n * `GET /api/native-updates/check` for native updates *and*\n * `POST /api/update` for OTA, which meant two sources of truth: the native\n * endpoint ignores the channel's assigned native version and the\n * `min_update_version` gate, so a device could be told to install an OTA\n * bundle its binary was too old to run.\n */\nexport interface DeviceFacts {\n appId: string;\n platform: ReturnType<typeof getPlatform>;\n channel?: string | undefined;\n isProd: boolean;\n versionCode: number;\n versionName: string;\n deviceId: string;\n pluginVersion?: string | undefined;\n versionOs?: string | undefined;\n isEmulator?: boolean | undefined;\n customId?: string | undefined;\n}\n\n/**\n * Assemble the check payload.\n *\n * Pure, and separate from the plugin calls that gather the facts, so the shape\n * of what goes on the wire can be tested without a device. Fields the app could\n * not determine are *omitted* rather than sent empty: the server writes only the\n * keys it receives, so a placeholder would overwrite a better value that an\n * earlier, better-informed check had already stored.\n */\nexport function buildCheckRequest(facts: DeviceFacts): UpdateCheckRequest {\n const request: UpdateCheckRequest = {\n appId: facts.appId,\n platform: facts.platform,\n versionCode: String(facts.versionCode),\n // Historical alias. The server accepts either and prefers versionCode.\n versionBuild: String(facts.versionCode),\n version_name: facts.versionName,\n deviceId: facts.deviceId,\n isProd: facts.isProd,\n };\n\n if (facts.channel) {\n request.channel = facts.channel;\n request.defaultChannel = facts.channel;\n }\n if (facts.pluginVersion) request.pluginVersion = facts.pluginVersion;\n if (facts.versionOs) request.versionOs = facts.versionOs;\n if (typeof facts.isEmulator === \"boolean\") request.isEmulator = facts.isEmulator;\n if (facts.customId) request.customId = facts.customId;\n\n return request;\n}\n\nexport async function checkForUpdate(): Promise<ResolvedUpdate | null> {\n if (!isNative()) return null;\n\n const config = getUpdaterConfig();\n const problems = describeConfigProblems(config);\n if (problems.length > 0) throw new UpdaterConfigError(problems);\n\n const [versionCode, versionName, deviceId, pluginVersion, osFacts] = await Promise.all([\n getVersionCode(),\n getBundleVersion(),\n getDeviceId(),\n getPluginVersion(),\n getOsFacts(),\n ]);\n\n const request = buildCheckRequest({\n appId: config.appId,\n platform: getPlatform(),\n channel: config.channel,\n isProd: config.environment === \"prod\",\n versionCode,\n versionName,\n deviceId,\n pluginVersion,\n ...osFacts,\n });\n\n const response = await postJson<UpdateCheckResponse>(\n `${config.apiUrl}/api/update`,\n request,\n config.timeoutMs,\n );\n\n // \"Channel not found\" and \"Environment mismatch\" are deployment mistakes.\n // Treating them as \"no update\" is how a broken channel goes unnoticed for\n // weeks.\n if (isBlockingResponse(response)) throw new UpdateCheckBlockedError(response);\n\n return resolveUpdate(response);\n}\n\n/**\n * Records a native update lifecycle event.\n *\n * Best effort: analytics must never break an update. OTA events are reported\n * by the plugin itself through its `statsUrl`, so only native ones are sent\n * from here.\n */\nexport async function logUpdateEvent(\n event: UpdateEvent,\n update: ResolvedUpdate,\n details?: { error?: string },\n): Promise<void> {\n if (update.kind !== \"native\") return;\n\n const config = getUpdaterConfig();\n if (!config.apiUrl) return;\n\n const payload: UpdateEventPayload = {\n event,\n platform: getPlatform(),\n device_id: await getDeviceId(),\n current_version_code: await getVersionCode(),\n new_version: update.version,\n new_version_code: update.versionCode,\n channel: config.channel,\n environment: String(config.environment),\n ...details,\n };\n\n try {\n await postJson(`${config.apiUrl}/api/native-updates/log`, payload, config.timeoutMs);\n } catch (error) {\n console.warn(\"[capuchoo] could not record update event\", error);\n }\n}\n","import type { PluginListenerHandle } from \"@capacitor/core\";\nimport { FileTransfer } from \"@capacitor/file-transfer\";\nimport { Directory, Filesystem } from \"@capacitor/filesystem\";\nimport { Network } from \"@capacitor/network\";\nimport type { ResolvedUpdate } from \"@capuchoo/core\";\nimport { getUpdaterConfig } from \"./config.js\";\n\nexport interface DownloadProgress {\n loaded: number;\n total: number;\n percent: number;\n}\n\n/** Cache file name prefix, derived from the app id so two flavours never collide. */\nfunction cachePrefix(): string {\n const { appId, appName } = getUpdaterConfig();\n const base = appId || appName;\n return `${base.replaceAll(/[^\\w.-]/g, \"-\")}-`;\n}\n\nfunction apkFileName(update: ResolvedUpdate): string {\n return `${cachePrefix()}${update.version}-${update.versionCode ?? 0}.apk`;\n}\n\n/**\n * Downloads a native APK into the app cache and returns its path.\n *\n * The file name embeds the app id, so a staging build and a production build\n * installed side by side cannot overwrite each other's download. The previous\n * implementation prefixed every file with a hard-coded app name.\n */\nexport async function downloadNativeUpdate(\n update: ResolvedUpdate,\n onProgress: (progress: DownloadProgress) => void,\n): Promise<string> {\n if (!update.downloadUrl) {\n throw new Error(\"This update has no download URL\");\n }\n\n const network = await Network.getStatus();\n if (!network.connected) {\n throw new Error(\"Connect to the internet to download this update\");\n }\n\n // Clear older APKs first: they are typically 20-60 MB and the OS can evict\n // the cache mid-download if it is already full.\n await cleanApkCache();\n\n const fileName = apkFileName(update);\n const destination = await Filesystem.getUri({\n directory: Directory.Cache,\n path: fileName,\n });\n\n let progressListener: PluginListenerHandle | null = null;\n\n try {\n progressListener = await FileTransfer.addListener(\"progress\", (event) => {\n if (event.url !== update.downloadUrl) return;\n\n const percent =\n event.lengthComputable && event.contentLength > 0\n ? Math.round((event.bytes / event.contentLength) * 100)\n : 0;\n onProgress({ loaded: event.bytes, total: event.contentLength, percent });\n });\n\n const result = await FileTransfer.downloadFile({\n url: update.downloadUrl,\n path: destination.uri,\n progress: true,\n connectTimeout: 60_000,\n readTimeout: 300_000,\n });\n\n return result.path || destination.uri;\n } finally {\n await progressListener?.remove();\n }\n}\n\n/** Removes this app's cached APKs. Failures are non-fatal. */\nexport async function cleanApkCache(): Promise<void> {\n const prefix = cachePrefix();\n\n try {\n const { files } = await Filesystem.readdir({\n directory: Directory.Cache,\n path: \"\",\n });\n\n await Promise.all(\n files\n .filter((file) => file.name.startsWith(prefix) && file.name.endsWith(\".apk\"))\n .map((file) => Filesystem.deleteFile({ directory: Directory.Cache, path: file.name })),\n );\n } catch (error) {\n console.warn(\"[capuchoo] could not clean the APK cache\", error);\n }\n}\n","import { Capacitor } from \"@capacitor/core\";\nimport { FileOpener } from \"@capawesome-team/capacitor-file-opener\";\nimport { getUpdaterConfig } from \"./config.js\";\n\nconst APK_MIME = \"application/vnd.android.package-archive\";\n\n/**\n * Hands a downloaded APK to the Android package installer.\n *\n * Requires `REQUEST_INSTALL_PACKAGES` in the manifest - the Trapeze config for\n * each flavour merges it in - and the user must have allowed this app to\n * install unknown apps. Both failures surface as opaque platform errors, so\n * they are translated into something a user can act on.\n */\nexport async function openNativeInstaller(path: string): Promise<void> {\n if (Capacitor.getPlatform() !== \"android\") {\n throw new Error(\n \"Installing a native update from inside the app is only possible on Android. \" +\n \"On iOS the update has to come from the App Store or TestFlight.\",\n );\n }\n\n try {\n await FileOpener.openFile({ path, mimeType: APK_MIME });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const { appName } = getUpdaterConfig();\n\n if (/permission|unknown sources|REQUEST_INSTALL_PACKAGES/i.test(message)) {\n throw new Error(\n `Allow ${appName} to install unknown apps in Android settings, then try again`,\n );\n }\n if (/activity|no app/i.test(message)) {\n throw new Error(\n \"No package installer is available on this device, so the update cannot be installed here\",\n );\n }\n\n throw new Error(`Could not open the Android installer: ${message}`);\n }\n}\n","import { CapacitorUpdater } from \"@capgo/capacitor-updater\";\nimport type { ResolvedUpdate } from \"@capuchoo/core\";\nimport { isNative } from \"./device.js\";\n\n/**\n * Thin wrapper over the OTA plugin.\n *\n * Downloading and applying a web bundle stays with `@capgo/capacitor-updater`:\n * it owns the native bundle store, the atomic swap and the rollback. This\n * module only sequences those calls correctly.\n */\n\n/**\n * Confirms the current bundle booted successfully.\n *\n * **This must be called once, early, on every app start.** If the plugin does\n * not hear it within `appReadyTimeout`, it assumes the new bundle crashed and\n * rolls back to the previous one - which looks exactly like \"the update did\n * not install\".\n */\nexport async function notifyAppReady(): Promise<void> {\n if (!isNative()) return;\n\n try {\n await CapacitorUpdater.notifyAppReady();\n } catch (error) {\n console.warn(\"[capuchoo] could not mark this bundle as ready\", error);\n }\n}\n\n/** The bundle currently applied, or null off-device. */\nexport async function getCurrentBundle() {\n if (!isNative()) return null;\n\n try {\n return await CapacitorUpdater.current();\n } catch (error) {\n console.warn(\"[capuchoo] could not read the current bundle\", error);\n return null;\n }\n}\n\n/**\n * Downloads an OTA bundle and applies it.\n *\n * `set` swaps the active bundle and reloads the WebView, so nothing after it\n * runs. It is called last on purpose.\n */\nexport async function applyOtaUpdate(update: ResolvedUpdate): Promise<void> {\n if (update.kind !== \"ota\") {\n throw new Error(\"applyOtaUpdate was given a native update\");\n }\n\n // Already downloaded - either by a previous attempt in this session, or by\n // the plugin's own background download when autoUpdate is \"onlyDownload\".\n if (update.bundleId) {\n await CapacitorUpdater.set({ id: update.bundleId });\n return;\n }\n\n if (!update.downloadUrl) {\n throw new Error(\"This update has no download URL\");\n }\n\n const bundle = await CapacitorUpdater.download({\n url: update.downloadUrl,\n version: update.version,\n ...(update.checksum ? { checksum: update.checksum } : {}),\n ...(update.sessionKey ? { sessionKey: update.sessionKey } : {}),\n });\n\n update.bundleId = bundle.id;\n await CapacitorUpdater.set({ id: bundle.id });\n}\n\n/** Discards a downloaded bundle that will not be applied. */\nexport async function discardBundle(bundleId: string): Promise<void> {\n if (!isNative()) return;\n\n try {\n await CapacitorUpdater.delete({ id: bundleId });\n } catch (error) {\n console.warn(\"[capuchoo] could not delete bundle\", bundleId, error);\n }\n}\n"],"mappings":"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"}
|
package/dist/vue.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as openNativeInstaller, c as UpdateCheckBlockedError, d as
|
|
1
|
+
import { S as getUpdaterConfig, a as openNativeInstaller, c as UpdateCheckBlockedError, d as checkForUpdate, f as logUpdateEvent, i as notifyAppReady, l as UpdaterConfigError, o as cleanApkCache, r as getCurrentBundle, s as downloadNativeUpdate, t as applyOtaUpdate, y as isNative } from "./ota.service-tv6eNQcB.js";
|
|
2
2
|
import { CapacitorUpdater } from "@capgo/capacitor-updater";
|
|
3
3
|
import { computed, readonly, ref } from "vue";
|
|
4
4
|
//#region src/vue/useUpdater.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capuchoo/updater",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "App-side runtime for Capucho OTA and native updates in Capacitor apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"capacitor",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"access": "public"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@capuchoo/core": "0.1.
|
|
45
|
+
"@capuchoo/core": "^0.1.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@capacitor/app": "^8.0.0",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"peerDependencies": {
|
|
60
60
|
"@capacitor/app": "^8.0.0",
|
|
61
61
|
"@capacitor/core": "^8.0.0",
|
|
62
|
+
"@capacitor/device": "^8.0.0",
|
|
62
63
|
"@capacitor/file-transfer": "^2.0.5",
|
|
63
64
|
"@capacitor/filesystem": "^8.1.2",
|
|
64
65
|
"@capacitor/network": "^8.0.1",
|
|
@@ -69,6 +70,9 @@
|
|
|
69
70
|
"peerDependenciesMeta": {
|
|
70
71
|
"vue": {
|
|
71
72
|
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"@capacitor/device": {
|
|
75
|
+
"optional": true
|
|
72
76
|
}
|
|
73
77
|
},
|
|
74
78
|
"scripts": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ota.service-t2_hIxcy.js","names":[],"sources":["../src/config.ts","../src/device.ts","../src/api.service.ts","../src/download.service.ts","../src/install.service.ts","../src/ota.service.ts"],"sourcesContent":["/**\n * Runtime configuration for the updater.\n *\n * Values come from the build's `VITE_*` variables, which the CLI injects from\n * the flavour's env file. `configureUpdater` lets an app override any of them\n * at startup - useful for tests and for apps that resolve their endpoint from\n * a login response rather than at build time.\n */\nexport interface UpdaterConfig {\n /** Base URL of the Capucho backend, with no trailing slash. */\n apiUrl: string;\n /** Bundle identifier of this build. Must match what the CLI published. */\n appId: string;\n /** Human-readable name, used in prompts and in the APK cache file name. */\n appName: string;\n /** Channel to consult. Bound to an environment server-side. */\n channel: string;\n /** Free-form: an app may use flavours beyond dev/staging/prod. */\n environment: string;\n /** Milliseconds before an update check is abandoned. */\n timeoutMs: number;\n}\n\nfunction env(key: string): string | undefined {\n // `import.meta.env` is replaced at build time by Vite. Guard the access so\n // the module can also be imported from Node (tests, SSR) without throwing.\n const source = (import.meta as ImportMeta & { env?: Record<string, string | undefined> }).env;\n return source?.[key];\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nlet overrides: Partial<UpdaterConfig> = {};\n\n/**\n * Overrides configuration resolved from the build. Call before `init`.\n * Passing `{}` clears previous overrides.\n */\nexport function configureUpdater(next: Partial<UpdaterConfig>): void {\n overrides = { ...overrides, ...next };\n}\n\n/** @internal exposed for tests. */\nexport function resetUpdaterConfig(): void {\n overrides = {};\n}\n\nexport function getUpdaterConfig(): UpdaterConfig {\n const apiUrl = overrides.apiUrl ?? env(\"VITE_UPDATE_API_URL\") ?? \"\";\n\n return {\n apiUrl: stripTrailingSlash(apiUrl),\n appId: overrides.appId ?? env(\"VITE_APP_ID\") ?? \"\",\n appName: overrides.appName ?? env(\"VITE_APP_NAME\") ?? \"the app\",\n channel: overrides.channel ?? env(\"VITE_UPDATE_CHANNEL\") ?? \"prod\",\n environment:\n overrides.environment ?? env(\"VITE_ENVIRONMENT\") ?? (env(\"PROD\") === \"true\" ? \"prod\" : \"dev\"),\n timeoutMs: overrides.timeoutMs ?? 30_000,\n };\n}\n\n/**\n * Reasons the updater cannot run, as user-facing strings.\n *\n * The previous implementation defaulted `apiUrl` to a hard-coded Render URL and\n * `appId` to a hard-coded bundle id. A build with a missing variable therefore\n * silently pointed at somebody else's backend, or asked for the wrong app, and\n * reported \"you are up to date\". Failing loudly is the whole point of this\n * function.\n */\nexport function describeConfigProblems(config: UpdaterConfig): string[] {\n const problems: string[] = [];\n if (!config.apiUrl) {\n problems.push(\"VITE_UPDATE_API_URL is not set, so updates cannot be checked\");\n }\n if (!config.appId) {\n problems.push(\"VITE_APP_ID is not set, so the server cannot identify this build\");\n }\n return problems;\n}\n","import { App } from \"@capacitor/app\";\nimport { Capacitor } from \"@capacitor/core\";\nimport { CapacitorUpdater } from \"@capgo/capacitor-updater\";\n\n/**\n * Facts about the running build that the server needs in order to decide\n * whether an update applies.\n */\n\n/**\n * Native build number of the installed binary.\n *\n * Returns 0 off-device. The old implementation returned 999999 on web, which\n * meant a browser session claimed to be newer than every published release and\n * so never saw an update - masking the very bug you would be debugging.\n */\nexport async function getVersionCode(): Promise<number> {\n if (!Capacitor.isNativePlatform()) return 0;\n\n try {\n const info = await App.getInfo();\n return Number.parseInt(info.build, 10) || 0;\n } catch {\n return 0;\n }\n}\n\n/**\n * Semantic version of the web bundle currently applied.\n *\n * `\"builtin\"` means no OTA bundle has been applied yet and the app is running\n * the assets compiled into the binary. The server treats it as 0.0.0, so it\n * must be reported honestly rather than sent as a constant.\n */\nexport async function getBundleVersion(): Promise<string> {\n if (!Capacitor.isNativePlatform()) return \"builtin\";\n\n try {\n const current = await CapacitorUpdater.current();\n return current.bundle.version || \"builtin\";\n } catch {\n return \"builtin\";\n }\n}\n\n/**\n * Stable per-install identifier, supplied by the OTA plugin.\n *\n * The plugin persists this natively. Reading `localStorage.device_id` instead -\n * as the app template did - returns null on a fresh install and is wiped\n * whenever the WebView data is cleared, so channel overrides and per-device\n * stats silently stopped working.\n */\nexport async function getDeviceId(): Promise<string> {\n if (!Capacitor.isNativePlatform()) return \"web\";\n\n try {\n const { deviceId } = await CapacitorUpdater.getDeviceId();\n return deviceId || \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nexport function getPlatform(): \"android\" | \"ios\" | \"web\" {\n return Capacitor.getPlatform() as \"android\" | \"ios\" | \"web\";\n}\n\nexport function isNative(): boolean {\n return Capacitor.isNativePlatform();\n}\n","import {\n isBlockingResponse,\n resolveUpdate,\n type ResolvedUpdate,\n type UpdateCheckRequest,\n type UpdateCheckResponse,\n type UpdateEvent,\n type UpdateEventPayload,\n} from \"@capuchoo/core\";\nimport { getUpdaterConfig, describeConfigProblems } from \"./config.js\";\nimport { getBundleVersion, getDeviceId, getPlatform, getVersionCode, isNative } from \"./device.js\";\n\n/** Raised when the updater is misconfigured, rather than reporting \"up to date\". */\nexport class UpdaterConfigError extends Error {\n readonly problems: string[];\n\n constructor(problems: string[]) {\n super(problems.join(\"; \"));\n this.name = \"UpdaterConfigError\";\n this.problems = problems;\n }\n}\n\n/** Raised when the server says the request itself cannot be served. */\nexport class UpdateCheckBlockedError extends Error {\n readonly response: UpdateCheckResponse;\n\n constructor(response: UpdateCheckResponse) {\n super(response.message ?? \"The update service rejected this request\");\n this.name = \"UpdateCheckBlockedError\";\n this.response = response;\n }\n}\n\nasync function postJson<T>(url: string, body: unknown, timeoutMs: number): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n try {\n const response = await fetch(url, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`${url} responded ${response.status} ${response.statusText}`);\n }\n\n return (await response.json()) as T;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Asks the server what this device should be running.\n *\n * One request, one endpoint. The app template used to call\n * `GET /api/native-updates/check` for native updates *and*\n * `POST /api/update` for OTA, which meant two sources of truth: the native\n * endpoint ignores the channel's assigned native version and the\n * `min_update_version` gate, so a device could be told to install an OTA\n * bundle its binary was too old to run.\n */\nexport async function checkForUpdate(): Promise<ResolvedUpdate | null> {\n if (!isNative()) return null;\n\n const config = getUpdaterConfig();\n const problems = describeConfigProblems(config);\n if (problems.length > 0) throw new UpdaterConfigError(problems);\n\n const [versionCode, versionName, deviceId] = await Promise.all([\n getVersionCode(),\n getBundleVersion(),\n getDeviceId(),\n ]);\n\n const request: UpdateCheckRequest = {\n appId: config.appId,\n platform: getPlatform(),\n channel: config.channel,\n defaultChannel: config.channel,\n versionCode: String(versionCode),\n versionBuild: String(versionCode),\n version_name: versionName,\n deviceId,\n isProd: config.environment === \"prod\",\n };\n\n const response = await postJson<UpdateCheckResponse>(\n `${config.apiUrl}/api/update`,\n request,\n config.timeoutMs,\n );\n\n // \"Channel not found\" and \"Environment mismatch\" are deployment mistakes.\n // Treating them as \"no update\" is how a broken channel goes unnoticed for\n // weeks.\n if (isBlockingResponse(response)) throw new UpdateCheckBlockedError(response);\n\n return resolveUpdate(response);\n}\n\n/**\n * Records a native update lifecycle event.\n *\n * Best effort: analytics must never break an update. OTA events are reported\n * by the plugin itself through its `statsUrl`, so only native ones are sent\n * from here.\n */\nexport async function logUpdateEvent(\n event: UpdateEvent,\n update: ResolvedUpdate,\n details?: { error?: string },\n): Promise<void> {\n if (update.kind !== \"native\") return;\n\n const config = getUpdaterConfig();\n if (!config.apiUrl) return;\n\n const payload: UpdateEventPayload = {\n event,\n platform: getPlatform(),\n device_id: await getDeviceId(),\n current_version_code: await getVersionCode(),\n new_version: update.version,\n new_version_code: update.versionCode,\n channel: config.channel,\n environment: String(config.environment),\n ...details,\n };\n\n try {\n await postJson(`${config.apiUrl}/api/native-updates/log`, payload, config.timeoutMs);\n } catch (error) {\n console.warn(\"[capuchoo] could not record update event\", error);\n }\n}\n","import type { PluginListenerHandle } from \"@capacitor/core\";\nimport { FileTransfer } from \"@capacitor/file-transfer\";\nimport { Directory, Filesystem } from \"@capacitor/filesystem\";\nimport { Network } from \"@capacitor/network\";\nimport type { ResolvedUpdate } from \"@capuchoo/core\";\nimport { getUpdaterConfig } from \"./config.js\";\n\nexport interface DownloadProgress {\n loaded: number;\n total: number;\n percent: number;\n}\n\n/** Cache file name prefix, derived from the app id so two flavours never collide. */\nfunction cachePrefix(): string {\n const { appId, appName } = getUpdaterConfig();\n const base = appId || appName;\n return `${base.replaceAll(/[^\\w.-]/g, \"-\")}-`;\n}\n\nfunction apkFileName(update: ResolvedUpdate): string {\n return `${cachePrefix()}${update.version}-${update.versionCode ?? 0}.apk`;\n}\n\n/**\n * Downloads a native APK into the app cache and returns its path.\n *\n * The file name embeds the app id, so a staging build and a production build\n * installed side by side cannot overwrite each other's download. The previous\n * implementation prefixed every file with a hard-coded app name.\n */\nexport async function downloadNativeUpdate(\n update: ResolvedUpdate,\n onProgress: (progress: DownloadProgress) => void,\n): Promise<string> {\n if (!update.downloadUrl) {\n throw new Error(\"This update has no download URL\");\n }\n\n const network = await Network.getStatus();\n if (!network.connected) {\n throw new Error(\"Connect to the internet to download this update\");\n }\n\n // Clear older APKs first: they are typically 20-60 MB and the OS can evict\n // the cache mid-download if it is already full.\n await cleanApkCache();\n\n const fileName = apkFileName(update);\n const destination = await Filesystem.getUri({\n directory: Directory.Cache,\n path: fileName,\n });\n\n let progressListener: PluginListenerHandle | null = null;\n\n try {\n progressListener = await FileTransfer.addListener(\"progress\", (event) => {\n if (event.url !== update.downloadUrl) return;\n\n const percent =\n event.lengthComputable && event.contentLength > 0\n ? Math.round((event.bytes / event.contentLength) * 100)\n : 0;\n onProgress({ loaded: event.bytes, total: event.contentLength, percent });\n });\n\n const result = await FileTransfer.downloadFile({\n url: update.downloadUrl,\n path: destination.uri,\n progress: true,\n connectTimeout: 60_000,\n readTimeout: 300_000,\n });\n\n return result.path || destination.uri;\n } finally {\n await progressListener?.remove();\n }\n}\n\n/** Removes this app's cached APKs. Failures are non-fatal. */\nexport async function cleanApkCache(): Promise<void> {\n const prefix = cachePrefix();\n\n try {\n const { files } = await Filesystem.readdir({\n directory: Directory.Cache,\n path: \"\",\n });\n\n await Promise.all(\n files\n .filter((file) => file.name.startsWith(prefix) && file.name.endsWith(\".apk\"))\n .map((file) => Filesystem.deleteFile({ directory: Directory.Cache, path: file.name })),\n );\n } catch (error) {\n console.warn(\"[capuchoo] could not clean the APK cache\", error);\n }\n}\n","import { Capacitor } from \"@capacitor/core\";\nimport { FileOpener } from \"@capawesome-team/capacitor-file-opener\";\nimport { getUpdaterConfig } from \"./config.js\";\n\nconst APK_MIME = \"application/vnd.android.package-archive\";\n\n/**\n * Hands a downloaded APK to the Android package installer.\n *\n * Requires `REQUEST_INSTALL_PACKAGES` in the manifest - the Trapeze config for\n * each flavour merges it in - and the user must have allowed this app to\n * install unknown apps. Both failures surface as opaque platform errors, so\n * they are translated into something a user can act on.\n */\nexport async function openNativeInstaller(path: string): Promise<void> {\n if (Capacitor.getPlatform() !== \"android\") {\n throw new Error(\n \"Installing a native update from inside the app is only possible on Android. \" +\n \"On iOS the update has to come from the App Store or TestFlight.\",\n );\n }\n\n try {\n await FileOpener.openFile({ path, mimeType: APK_MIME });\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n const { appName } = getUpdaterConfig();\n\n if (/permission|unknown sources|REQUEST_INSTALL_PACKAGES/i.test(message)) {\n throw new Error(\n `Allow ${appName} to install unknown apps in Android settings, then try again`,\n );\n }\n if (/activity|no app/i.test(message)) {\n throw new Error(\n \"No package installer is available on this device, so the update cannot be installed here\",\n );\n }\n\n throw new Error(`Could not open the Android installer: ${message}`);\n }\n}\n","import { CapacitorUpdater } from \"@capgo/capacitor-updater\";\nimport type { ResolvedUpdate } from \"@capuchoo/core\";\nimport { isNative } from \"./device.js\";\n\n/**\n * Thin wrapper over the OTA plugin.\n *\n * Downloading and applying a web bundle stays with `@capgo/capacitor-updater`:\n * it owns the native bundle store, the atomic swap and the rollback. This\n * module only sequences those calls correctly.\n */\n\n/**\n * Confirms the current bundle booted successfully.\n *\n * **This must be called once, early, on every app start.** If the plugin does\n * not hear it within `appReadyTimeout`, it assumes the new bundle crashed and\n * rolls back to the previous one - which looks exactly like \"the update did\n * not install\".\n */\nexport async function notifyAppReady(): Promise<void> {\n if (!isNative()) return;\n\n try {\n await CapacitorUpdater.notifyAppReady();\n } catch (error) {\n console.warn(\"[capuchoo] could not mark this bundle as ready\", error);\n }\n}\n\n/** The bundle currently applied, or null off-device. */\nexport async function getCurrentBundle() {\n if (!isNative()) return null;\n\n try {\n return await CapacitorUpdater.current();\n } catch (error) {\n console.warn(\"[capuchoo] could not read the current bundle\", error);\n return null;\n }\n}\n\n/**\n * Downloads an OTA bundle and applies it.\n *\n * `set` swaps the active bundle and reloads the WebView, so nothing after it\n * runs. It is called last on purpose.\n */\nexport async function applyOtaUpdate(update: ResolvedUpdate): Promise<void> {\n if (update.kind !== \"ota\") {\n throw new Error(\"applyOtaUpdate was given a native update\");\n }\n\n // Already downloaded - either by a previous attempt in this session, or by\n // the plugin's own background download when autoUpdate is \"onlyDownload\".\n if (update.bundleId) {\n await CapacitorUpdater.set({ id: update.bundleId });\n return;\n }\n\n if (!update.downloadUrl) {\n throw new Error(\"This update has no download URL\");\n }\n\n const bundle = await CapacitorUpdater.download({\n url: update.downloadUrl,\n version: update.version,\n ...(update.checksum ? { checksum: update.checksum } : {}),\n ...(update.sessionKey ? { sessionKey: update.sessionKey } : {}),\n });\n\n update.bundleId = bundle.id;\n await CapacitorUpdater.set({ id: bundle.id });\n}\n\n/** Discards a downloaded bundle that will not be applied. */\nexport async function discardBundle(bundleId: string): Promise<void> {\n if (!isNative()) return;\n\n try {\n await CapacitorUpdater.delete({ id: bundleId });\n } catch (error) {\n console.warn(\"[capuchoo] could not delete bundle\", bundleId, error);\n }\n}\n"],"mappings":"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"}
|