@absolutejs/absolute 0.20.0-beta.6 → 0.20.0-beta.61

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.
Files changed (179) hide show
  1. package/README.md +205 -0
  2. package/dist/angular/browser.js +392 -405
  3. package/dist/angular/browser.js.map +3 -3
  4. package/dist/angular/components/core/streamingSlotRegistrar.js +7 -7
  5. package/dist/angular/components/core/streamingSlotRegistry.js +4 -4
  6. package/dist/angular/index.js +379 -321
  7. package/dist/angular/index.js.map +8 -7
  8. package/dist/angular/server.js +133 -61
  9. package/dist/angular/server.js.map +8 -7
  10. package/dist/build.js +2336 -1347
  11. package/dist/build.js.map +22 -17
  12. package/dist/cli/config/client.js +12 -12
  13. package/dist/cli/config/server.js +1140 -1091
  14. package/dist/cli/index.js +10107 -2126
  15. package/dist/client/chunks/angularPatch-arh4sr68.js +157 -0
  16. package/dist/client/chunks/angularPatch-arh4sr68.js.map +10 -0
  17. package/dist/client/chunks/index-5py90ctf.js +87 -0
  18. package/dist/client/chunks/index-5py90ctf.js.map +10 -0
  19. package/dist/client/chunks/index-m35nbkdp.js +374 -0
  20. package/dist/client/chunks/index-m35nbkdp.js.map +12 -0
  21. package/dist/client/chunks/index-qxjc7kc5.js +12 -0
  22. package/dist/client/chunks/index-qxjc7kc5.js.map +9 -0
  23. package/dist/client/chunks/islands-xj2n8nwf.js +281 -0
  24. package/dist/client/chunks/islands-xj2n8nwf.js.map +14 -0
  25. package/dist/client/chunks/react-pdg4325x.js +65 -0
  26. package/dist/client/chunks/react-pdg4325x.js.map +10 -0
  27. package/dist/client/chunks/svelte-s0c964kb.js +19 -0
  28. package/dist/client/chunks/svelte-s0c964kb.js.map +10 -0
  29. package/dist/client/chunks/vue-rdskrnyg.js +24 -0
  30. package/dist/client/chunks/vue-rdskrnyg.js.map +10 -0
  31. package/dist/client/index.js +73 -841
  32. package/dist/client/index.js.map +7 -20
  33. package/dist/core/streamingSlotRegistrar.js +15 -13
  34. package/dist/core/streamingSlotRegistrar.js.map +2 -2
  35. package/dist/core/streamingSlotRegistry.js +11 -9
  36. package/dist/core/streamingSlotRegistry.js.map +2 -2
  37. package/dist/dev/client/hmrClient.ts +35 -3
  38. package/dist/dev/client/hmrTiming.ts +6 -0
  39. package/dist/dev/client/syncDevtools.ts +237 -0
  40. package/dist/dev/serverBootstrap.js +28 -0
  41. package/dist/image-client/imageClient.js +7 -7
  42. package/dist/index.js +3756 -2361
  43. package/dist/index.js.map +38 -30
  44. package/dist/islands/browser.js +11 -25
  45. package/dist/islands/browser.js.map +2 -2
  46. package/dist/islands/index.js +55 -52
  47. package/dist/islands/index.js.map +5 -4
  48. package/dist/mobile/browser.js +429 -20
  49. package/dist/mobile/browser.js.map +8 -5
  50. package/dist/mobile/index.js +11287 -3348
  51. package/dist/mobile/index.js.map +47 -18
  52. package/dist/mobile/remoteMacAgentEntry.js +1374 -14
  53. package/dist/mobile/shellAuth.js +36 -0
  54. package/dist/mobile/shellBootstrap.js +1800 -0
  55. package/dist/mobile/shellExpoAuth.js +163 -0
  56. package/dist/mobile/shellExpoDevices.js +77 -0
  57. package/dist/mobile/shellExpoSync.js +104 -0
  58. package/dist/mobile/shellSync.js +184 -0
  59. package/dist/mobile/shellUpdate.js +532 -0
  60. package/dist/mobile/uiPrimitives.js +264 -0
  61. package/dist/react/browser.js +17 -59
  62. package/dist/react/browser.js.map +2 -2
  63. package/dist/react/components/browser/index.js +107 -107
  64. package/dist/react/components/index.js +118 -116
  65. package/dist/react/components/index.js.map +2 -2
  66. package/dist/react/hooks/index.js +11 -25
  67. package/dist/react/hooks/index.js.map +2 -2
  68. package/dist/react/index.js +110 -67
  69. package/dist/react/index.js.map +8 -7
  70. package/dist/react/jsxDevRuntimeCompat.js +11 -9
  71. package/dist/react/jsxDevRuntimeCompat.js.map +2 -2
  72. package/dist/react/jsxRuntimeCompat.js +11 -9
  73. package/dist/react/jsxRuntimeCompat.js.map +2 -2
  74. package/dist/react/router/browser.js +9 -7
  75. package/dist/react/router/browser.js.map +2 -2
  76. package/dist/react/router/index.js +9 -7
  77. package/dist/react/router/index.js.map +2 -2
  78. package/dist/react/server.js +67 -11
  79. package/dist/react/server.js.map +7 -6
  80. package/dist/src/build/pwa.d.ts +16 -0
  81. package/dist/src/cli/config/server.d.ts +1 -1
  82. package/dist/src/cli/instanceStatus.d.ts +1 -0
  83. package/dist/src/cli/scripts/dev.d.ts +2 -0
  84. package/dist/src/client/browserTranslation.d.ts +19 -0
  85. package/dist/src/client/index.d.ts +1 -0
  86. package/dist/src/core/browserTranslation.d.ts +6 -0
  87. package/dist/src/core/devBuild.d.ts +1 -0
  88. package/dist/src/core/prepare.d.ts +376 -0
  89. package/dist/src/dev/clientManager.d.ts +1 -0
  90. package/dist/src/dev/devCert.d.ts +6 -4
  91. package/dist/src/dev/serverEntryCopies.d.ts +3 -0
  92. package/dist/src/ember/browser.d.ts +0 -16
  93. package/dist/src/mobile/adaptiveShell.d.ts +27 -0
  94. package/dist/src/mobile/androidEmulatorController.d.ts +8 -1
  95. package/dist/src/mobile/androidRelease.d.ts +9 -2
  96. package/dist/src/mobile/androidTestReport.d.ts +14 -0
  97. package/dist/src/mobile/androidUpgradeConformance.d.ts +47 -0
  98. package/dist/src/mobile/androidWebView.d.ts +1 -0
  99. package/dist/src/mobile/browser.d.ts +2 -0
  100. package/dist/src/mobile/capacitorBundle.d.ts +19 -0
  101. package/dist/src/mobile/ciWorkflow.d.ts +26 -0
  102. package/dist/src/mobile/config.d.ts +10 -1
  103. package/dist/src/mobile/devDeviceAdapter.d.ts +3 -0
  104. package/dist/src/mobile/deviceCapabilities.d.ts +49 -0
  105. package/dist/src/mobile/expoBridge.d.ts +91 -0
  106. package/dist/src/mobile/expoDevController.d.ts +56 -0
  107. package/dist/src/mobile/expoProject.d.ts +16 -0
  108. package/dist/src/mobile/index.d.ts +23 -0
  109. package/dist/src/mobile/iosDeviceAcceptance.d.ts +32 -0
  110. package/dist/src/mobile/iosPhysicalDeviceTransport.d.ts +16 -0
  111. package/dist/src/mobile/iosRelease.d.ts +63 -3
  112. package/dist/src/mobile/iosSimulatorController.d.ts +20 -2
  113. package/dist/src/mobile/iosTestReport.d.ts +24 -0
  114. package/dist/src/mobile/mobileBundleInspection.d.ts +63 -0
  115. package/dist/src/mobile/mobileInspect.d.ts +142 -0
  116. package/dist/src/mobile/mobilePreview.d.ts +174 -0
  117. package/dist/src/mobile/mobilePreviewClient.d.ts +1 -0
  118. package/dist/src/mobile/nativeAuth.d.ts +6 -0
  119. package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
  120. package/dist/src/mobile/nativeDeviceCapabilities.d.ts +6 -0
  121. package/dist/src/mobile/nativeRoute.d.ts +13 -0
  122. package/dist/src/mobile/nativeTestReport.d.ts +91 -0
  123. package/dist/src/mobile/nativeUpdates.d.ts +4 -0
  124. package/dist/src/mobile/navigationLifecycle.d.ts +36 -0
  125. package/dist/src/mobile/navigationState.d.ts +53 -0
  126. package/dist/src/mobile/pageProtocol.d.ts +2 -0
  127. package/dist/src/mobile/releaseDoctor.d.ts +23 -1
  128. package/dist/src/mobile/remoteMacProtocol.d.ts +134 -1
  129. package/dist/src/mobile/remoteMacWire.d.ts +1 -1
  130. package/dist/src/mobile/shellAuth.d.ts +8 -1
  131. package/dist/src/mobile/shellBootstrap.d.ts +15 -2
  132. package/dist/src/mobile/shellExpoAuth.d.ts +12 -0
  133. package/dist/src/mobile/shellExpoDevices.d.ts +3 -0
  134. package/dist/src/mobile/shellExpoSync.d.ts +18 -0
  135. package/dist/src/mobile/shellHttp.d.ts +2 -0
  136. package/dist/src/mobile/shellPush.d.ts +11 -0
  137. package/dist/src/mobile/shellSync.d.ts +46 -1
  138. package/dist/src/mobile/shellUpdate.d.ts +2 -0
  139. package/dist/src/mobile/syncRemediation.d.ts +10 -0
  140. package/dist/src/mobile/syncSchema.d.ts +9 -0
  141. package/dist/src/mobile/transport.d.ts +15 -0
  142. package/dist/src/mobile/uiPrimitives.d.ts +37 -0
  143. package/dist/src/mobile/updateClient.d.ts +44 -0
  144. package/dist/src/mobile/updateProtocol.d.ts +51 -0
  145. package/dist/src/mobile/updatePublisher.d.ts +66 -0
  146. package/dist/src/mobile/updateRollout.d.ts +16 -0
  147. package/dist/src/mobile/updateRuntime.d.ts +27 -0
  148. package/dist/src/mobile/updateSigning.d.ts +20 -0
  149. package/dist/src/plugins/hmr.d.ts +3 -0
  150. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  151. package/dist/src/react/hooks/useMediaQuery.d.ts +1 -1
  152. package/dist/src/utils/imageProcessing.d.ts +3 -0
  153. package/dist/src/utils/loadConfig.d.ts +1 -0
  154. package/dist/src/utils/logger.d.ts +1 -0
  155. package/dist/src/utils/startupBanner.d.ts +1 -0
  156. package/dist/src/utils/userAgentFunctions.d.ts +1 -1
  157. package/dist/src/vue/browser.d.ts +1 -0
  158. package/dist/src/vue/browserTranslation.d.ts +5 -0
  159. package/dist/src/vue/index.d.ts +1 -0
  160. package/dist/svelte/browser.js +12 -26
  161. package/dist/svelte/browser.js.map +2 -2
  162. package/dist/svelte/index.js +104 -61
  163. package/dist/svelte/index.js.map +8 -7
  164. package/dist/svelte/server.js +84 -28
  165. package/dist/svelte/server.js.map +7 -6
  166. package/dist/types/build.d.ts +64 -3
  167. package/dist/types/cli.d.ts +1 -0
  168. package/dist/types/messages.d.ts +1 -1
  169. package/dist/vue/browser.js +116 -56
  170. package/dist/vue/browser.js.map +5 -3
  171. package/dist/vue/components/Image.js +9 -7
  172. package/dist/vue/components/Image.js.map +2 -2
  173. package/dist/vue/components/index.js +11 -9
  174. package/dist/vue/components/index.js.map +2 -2
  175. package/dist/vue/index.js +211 -94
  176. package/dist/vue/index.js.map +10 -7
  177. package/dist/vue/server.js +69 -13
  178. package/dist/vue/server.js.map +7 -6
  179. package/package.json +40 -14
@@ -0,0 +1,532 @@
1
+ // src/mobile/shellUpdate.ts
2
+ import { Directory, Filesystem } from "@capacitor/filesystem";
3
+ import { Preferences } from "@capacitor/preferences";
4
+ import { registerPlugin } from "@capacitor/core";
5
+
6
+ // src/mobile/updateProtocol.ts
7
+ var ABSOLUTE_MOBILE_UPDATE_FORMAT = 1;
8
+ var ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES = 32 * 1024 * 1024;
9
+ var ABSOLUTE_MOBILE_UPDATE_MAX_FILES = 1e4;
10
+ var ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES = 128 * 1024 * 1024;
11
+ var ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM = "ecdsa-p256-sha256";
12
+ var HASH_PATTERN = /^[a-f0-9]{64}$/u;
13
+ var RELEASE_PATTERN = /^amu_[a-f0-9]{64}$/u;
14
+ var KEY_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
15
+ var CHANNEL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
16
+ var isClassification = (value) => value === "bug-fix" || value === "content" || value === "security";
17
+ var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
18
+ var canonicalValue = (value) => {
19
+ if (Array.isArray(value))
20
+ return value.map(canonicalValue);
21
+ if (!object(value))
22
+ return value;
23
+ return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue(value[key])]));
24
+ };
25
+ var absoluteMobileUpdateSigningPayload = (manifest) => new TextEncoder().encode(canonicalizeAbsoluteMobileUpdate(manifest));
26
+ var canonicalizeAbsoluteMobileUpdate = (value) => JSON.stringify(canonicalValue(value));
27
+ var requireText = (value, field) => {
28
+ if (typeof value !== "string" || value.length === 0)
29
+ throw new TypeError(`${field} must be a non-empty string.`);
30
+ return value;
31
+ };
32
+ var normalizeAbsoluteMobileUpdatePath = (value) => {
33
+ const path = requireText(value, "Update file path").replaceAll("\\", "/");
34
+ if (path.startsWith("/") || path.includes("\x00") || path.split("/").some((segment) => !segment || segment === "." || segment === "..")) {
35
+ throw new TypeError("Update file paths must be normalized relative paths.");
36
+ }
37
+ return path;
38
+ };
39
+ var parseFile = (value) => {
40
+ if (!object(value))
41
+ throw new TypeError("Update files must be objects.");
42
+ const path = normalizeAbsoluteMobileUpdatePath(value.path);
43
+ if (typeof value.bytes !== "number" || !Number.isSafeInteger(value.bytes) || value.bytes < 0 || value.bytes > ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES) {
44
+ throw new TypeError(`Update file ${path} has an invalid byte length.`);
45
+ }
46
+ if (typeof value.sha256 !== "string" || !HASH_PATTERN.test(value.sha256))
47
+ throw new TypeError(`Update file ${path} has an invalid SHA-256 digest.`);
48
+ return { bytes: value.bytes, path, sha256: value.sha256 };
49
+ };
50
+ var parseAbsoluteMobileUnsignedUpdateManifest = (value) => {
51
+ if (!object(value) || value.format !== ABSOLUTE_MOBILE_UPDATE_FORMAT)
52
+ throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
53
+ const appId = requireText(value.appId, "Update appId");
54
+ const channel = requireText(value.channel, "Update channel");
55
+ if (!CHANNEL_PATTERN.test(channel))
56
+ throw new TypeError("Update channel contains unsupported characters.");
57
+ if (!isClassification(value.classification))
58
+ throw new TypeError("Update classification is invalid.");
59
+ const createdAt = requireText(value.createdAt, "Update createdAt");
60
+ if (!Number.isFinite(Date.parse(createdAt)) || new Date(createdAt).toISOString() !== createdAt)
61
+ throw new TypeError("Update createdAt must be a canonical ISO timestamp.");
62
+ if (!Array.isArray(value.files) || value.files.length === 0)
63
+ throw new TypeError("An update must contain at least one file.");
64
+ if (value.files.length > ABSOLUTE_MOBILE_UPDATE_MAX_FILES)
65
+ throw new TypeError("Update contains too many files.");
66
+ const files = value.files.map(parseFile);
67
+ const sorted = [...files].sort((left, right) => left.path.localeCompare(right.path));
68
+ if (files.some((file, index) => file.path !== sorted[index]?.path))
69
+ throw new TypeError("Update files must be sorted by path.");
70
+ if (new Set(files.map(({ path }) => path)).size !== files.length)
71
+ throw new TypeError("Update file paths must be unique.");
72
+ if (files.reduce((total, file) => total + file.bytes, 0) > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
73
+ throw new TypeError("Update exceeds the maximum uncompressed size.");
74
+ if (typeof value.releaseId !== "string" || !RELEASE_PATTERN.test(value.releaseId))
75
+ throw new TypeError("Update releaseId is invalid.");
76
+ if (typeof value.runtimeFingerprint !== "string" || !HASH_PATTERN.test(value.runtimeFingerprint))
77
+ throw new TypeError("Update runtime fingerprint is invalid.");
78
+ if (value.withinSubmittedPurpose !== true)
79
+ throw new TypeError("OTA updates must attest that they remain within the submitted app purpose.");
80
+ return {
81
+ appId,
82
+ channel,
83
+ classification: value.classification,
84
+ createdAt,
85
+ files,
86
+ format: ABSOLUTE_MOBILE_UPDATE_FORMAT,
87
+ releaseId: value.releaseId,
88
+ runtimeFingerprint: value.runtimeFingerprint,
89
+ withinSubmittedPurpose: true
90
+ };
91
+ };
92
+ var parseAbsoluteMobileUpdateManifest = (value) => {
93
+ if (!object(value))
94
+ throw new TypeError("Invalid AbsoluteJS mobile update manifest.");
95
+ const { signature: signatureValue, ...unsignedValue } = value;
96
+ const unsigned = parseAbsoluteMobileUnsignedUpdateManifest(unsignedValue);
97
+ if (!object(signatureValue))
98
+ throw new TypeError("Update signature is missing.");
99
+ if (signatureValue.algorithm !== ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM)
100
+ throw new TypeError("Update signature algorithm is unsupported.");
101
+ const keyId = requireText(signatureValue.keyId, "Update signature keyId");
102
+ if (!KEY_ID_PATTERN.test(keyId))
103
+ throw new TypeError("Update signature keyId is invalid.");
104
+ const signature = requireText(signatureValue.value, "Update signature value");
105
+ let signatureBytes;
106
+ try {
107
+ signatureBytes = Uint8Array.from(atob(signature), (character) => character.charCodeAt(0));
108
+ } catch {
109
+ signatureBytes = new Uint8Array;
110
+ }
111
+ if (!/^[A-Za-z0-9+/]+={0,2}$/u.test(signature) || signatureBytes.byteLength !== 64 || btoa(String.fromCharCode(...signatureBytes)) !== signature)
112
+ throw new TypeError("Update signature is not canonical base64.");
113
+ return {
114
+ ...unsigned,
115
+ signature: {
116
+ algorithm: ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
117
+ keyId,
118
+ value: signature
119
+ }
120
+ };
121
+ };
122
+ var unsignedAbsoluteMobileUpdate = (manifest) => {
123
+ const { signature: _signature, ...unsigned } = manifest;
124
+ return unsigned;
125
+ };
126
+
127
+ // src/mobile/updateClient.ts
128
+ var exactManifestUrl = (value) => {
129
+ const url = new URL(value);
130
+ if (url.protocol !== "https:" && url.hostname !== "localhost" && url.hostname !== "127.0.0.1")
131
+ throw new TypeError("Mobile update manifests require HTTPS outside loopback development.");
132
+ if (url.username || url.password || url.hash)
133
+ throw new TypeError("Mobile update manifest URLs cannot contain credentials or fragments.");
134
+ return url;
135
+ };
136
+ var fileUrl = (manifestUrl, releaseId, path) => {
137
+ const encodedPath = path.split("/").map(encodeURIComponent).join("/");
138
+ const base = new URL(`./${encodeURIComponent(releaseId)}/files/`, manifestUrl);
139
+ const result = new URL(encodedPath, base);
140
+ if (result.origin !== manifestUrl.origin || !result.pathname.startsWith(base.pathname))
141
+ throw new TypeError("Mobile update asset escaped its signed release origin.");
142
+ return result;
143
+ };
144
+ var readChunks = async (reader, maximum, chunks = [], received = 0) => {
145
+ const result = await reader.read();
146
+ if (result.done)
147
+ return { chunks, received };
148
+ const total = received + result.value.byteLength;
149
+ if (total > maximum)
150
+ throw new TypeError("Mobile update response exceeds its signed size.");
151
+ chunks.push(result.value);
152
+ return readChunks(reader, maximum, chunks, total);
153
+ };
154
+ var readBounded = async (response, maximum) => {
155
+ const declared = Number(response.headers.get("content-length"));
156
+ if (Number.isFinite(declared) && declared > maximum)
157
+ throw new TypeError("Mobile update response exceeds its signed size.");
158
+ if (!response.body)
159
+ return new Uint8Array;
160
+ const reader = response.body.getReader();
161
+ let result;
162
+ try {
163
+ result = await readChunks(reader, maximum);
164
+ } catch (error) {
165
+ await reader.cancel().catch(() => {
166
+ return;
167
+ });
168
+ throw error;
169
+ }
170
+ const contents = new Uint8Array(result.received);
171
+ let offset = 0;
172
+ for (const chunk of result.chunks) {
173
+ contents.set(chunk, offset);
174
+ offset += chunk.byteLength;
175
+ }
176
+ return contents;
177
+ };
178
+ var requestHeaders = (config) => ({
179
+ "x-absolute-mobile-app": config.appId,
180
+ "x-absolute-mobile-channel": config.channel,
181
+ "x-absolute-mobile-installation": config.installationId,
182
+ "x-absolute-mobile-release": config.currentReleaseId,
183
+ "x-absolute-mobile-runtime": config.runtimeFingerprint
184
+ });
185
+ var requireCompatible = (manifest, config) => {
186
+ if (manifest.appId !== config.appId)
187
+ throw new TypeError("Mobile update belongs to another app.");
188
+ if (manifest.channel !== config.channel)
189
+ throw new TypeError("Mobile update belongs to another channel.");
190
+ if (manifest.runtimeFingerprint !== config.runtimeFingerprint)
191
+ throw new TypeError("Mobile update requires a different native runtime.");
192
+ };
193
+ var createAbsoluteMobileUpdateClient = (options) => {
194
+ const manifestUrl = exactManifestUrl(options.config.manifestUrl);
195
+ const request = options.fetch ?? globalThis.fetch;
196
+ const downloadFiles = async (manifest, index = 0, received = 0) => {
197
+ const file = manifest.files[index];
198
+ if (!file)
199
+ return received;
200
+ const asset = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
201
+ cache: "no-store",
202
+ credentials: "omit",
203
+ redirect: "error",
204
+ signal: AbortSignal.timeout(30000)
205
+ });
206
+ if (!asset.ok)
207
+ throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset.status}.`);
208
+ const contents = await readBounded(asset, file.bytes);
209
+ const total = received + contents.byteLength;
210
+ if (contents.byteLength !== file.bytes || total > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
211
+ throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
212
+ if (await options.verifier.digest(contents) !== file.sha256)
213
+ throw new TypeError(`Mobile update asset ${file.path} failed integrity verification.`);
214
+ await options.store.write(file, contents);
215
+ return downloadFiles(manifest, index + 1, total);
216
+ };
217
+ const check = async (download = false) => {
218
+ const response = await request(manifestUrl, {
219
+ cache: "no-store",
220
+ credentials: "omit",
221
+ headers: requestHeaders(options.config),
222
+ redirect: "error",
223
+ signal: AbortSignal.timeout(15000)
224
+ });
225
+ if (response.status === 204 || response.status === 304)
226
+ return { kind: "current" };
227
+ if (!response.ok)
228
+ throw new TypeError(`Mobile update check failed with HTTP ${response.status}.`);
229
+ const manifestBytes = await readBounded(response, 1024 * 1024);
230
+ let manifestValue;
231
+ try {
232
+ manifestValue = JSON.parse(new TextDecoder().decode(manifestBytes));
233
+ } catch {
234
+ throw new TypeError("Mobile update manifest is not valid JSON.");
235
+ }
236
+ const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
237
+ requireCompatible(manifest, options.config);
238
+ if (!await options.verifier.verify(manifest))
239
+ throw new TypeError("Mobile update signature verification failed.");
240
+ if (manifest.releaseId === options.config.currentReleaseId)
241
+ return { kind: "current" };
242
+ if (options.config.blockedReleaseIds?.includes(manifest.releaseId))
243
+ return { kind: "quarantined", releaseId: manifest.releaseId };
244
+ if (!download)
245
+ return { kind: "update-available", manifest };
246
+ await options.store.begin(manifest);
247
+ try {
248
+ await downloadFiles(manifest);
249
+ await options.store.commit(manifest);
250
+ } catch (error) {
251
+ await options.store.abort(manifest.releaseId);
252
+ throw error;
253
+ }
254
+ return { kind: "downloaded", manifest };
255
+ };
256
+ return {
257
+ check,
258
+ activate: (releaseId) => options.store.activate(releaseId),
259
+ download: () => check(true)
260
+ };
261
+ };
262
+
263
+ // src/mobile/shellUpdate.ts
264
+ var STATE_KEY = "absolute.mobile.update.state.v1";
265
+ var INSTALLATION_KEY = "absolute.mobile.update.installation.v1";
266
+ var ROOT = "NoCloud/ionic_built_snapshots";
267
+ var watchdog = registerPlugin("AbsoluteMobileUpdateWatchdog");
268
+ var base64Bytes = (value) => Uint8Array.from(atob(value), (character) => character.charCodeAt(0));
269
+ var arrayBuffer = (value) => new Uint8Array(value).buffer;
270
+ var bytesBase64 = (value) => {
271
+ let result = "";
272
+ const size = 32768;
273
+ for (let offset = 0;offset < value.length; offset += size) {
274
+ result += String.fromCharCode(...value.subarray(offset, offset + size));
275
+ }
276
+ return btoa(result);
277
+ };
278
+ var readState = async () => {
279
+ const { value } = await Preferences.get({ key: STATE_KEY });
280
+ if (!value)
281
+ return {};
282
+ try {
283
+ const parsed = JSON.parse(value);
284
+ if (typeof parsed !== "object" || parsed === null)
285
+ return {};
286
+ const text = (key) => {
287
+ const candidate = Reflect.get(parsed, key);
288
+ return typeof candidate === "string" ? candidate : undefined;
289
+ };
290
+ const number = (key) => {
291
+ const candidate = Reflect.get(parsed, key);
292
+ return typeof candidate === "number" && Number.isFinite(candidate) ? candidate : undefined;
293
+ };
294
+ const recoveryValue = Reflect.get(parsed, "recovery");
295
+ const quarantineValue = Reflect.get(parsed, "quarantinedReleases");
296
+ const quarantinedReleases = Array.isArray(quarantineValue) ? [
297
+ ...new Set(quarantineValue.filter((candidateRelease) => typeof candidateRelease === "string" && /^amu_[a-f0-9]{64}$/u.test(candidateRelease)))
298
+ ].slice(-8) : [];
299
+ const recovery = typeof recoveryValue === "object" && recoveryValue !== null ? {
300
+ durationMs: Reflect.get(recoveryValue, "durationMs"),
301
+ reason: Reflect.get(recoveryValue, "reason"),
302
+ releaseId: Reflect.get(recoveryValue, "releaseId")
303
+ } : undefined;
304
+ let validRecovery;
305
+ if (recovery && typeof recovery.durationMs === "number" && Number.isFinite(recovery.durationMs) && recovery.durationMs >= 0 && (recovery.reason === "boot-interrupted" || recovery.reason === "boot-timeout") && typeof recovery.releaseId === "string")
306
+ validRecovery = {
307
+ durationMs: recovery.durationMs,
308
+ reason: recovery.reason,
309
+ releaseId: recovery.releaseId
310
+ };
311
+ return {
312
+ ...text("activeRelease") ? { activeRelease: text("activeRelease") } : {},
313
+ ...text("pendingRelease") ? { pendingRelease: text("pendingRelease") } : {},
314
+ ...number("pendingStartedAt") === undefined ? {} : { pendingStartedAt: number("pendingStartedAt") },
315
+ ...text("previousPath") ? { previousPath: text("previousPath") } : {},
316
+ ...quarantinedReleases.length > 0 ? { quarantinedReleases } : {},
317
+ ...text("readyRelease") ? { readyRelease: text("readyRelease") } : {},
318
+ ...validRecovery ? { recovery: validRecovery } : {}
319
+ };
320
+ } catch {
321
+ return {};
322
+ }
323
+ };
324
+ var writeState = (state) => Preferences.set({ key: STATE_KEY, value: JSON.stringify(state) });
325
+ var installationId = async () => {
326
+ const existing = await Preferences.get({ key: INSTALLATION_KEY });
327
+ if (existing.value && /^[a-f0-9-]{36}$/u.test(existing.value))
328
+ return existing.value;
329
+ const value = crypto.randomUUID();
330
+ await Preferences.set({ key: INSTALLATION_KEY, value });
331
+ return value;
332
+ };
333
+ var isIonicWebView = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "getServerBasePath") === "function" && typeof Reflect.get(value, "setServerBasePath") === "function" && typeof Reflect.get(value, "persistServerBasePath") === "function";
334
+ var webView = () => {
335
+ const ionic = Reflect.get(globalThis, "Ionic");
336
+ const provider = typeof ionic === "object" && ionic !== null ? Reflect.get(ionic, "WebView") : undefined;
337
+ if (!isIonicWebView(provider))
338
+ throw new TypeError("Capacitor WebView update controls are unavailable.");
339
+ return provider;
340
+ };
341
+ var currentServerBasePath = () => new Promise((resolve) => webView().getServerBasePath(resolve));
342
+ var releasePath = (releaseId) => `${ROOT}/${releaseId}`;
343
+ var releaseNativePath = async (releaseId) => {
344
+ const { uri } = await Filesystem.getUri({
345
+ directory: Directory.Library,
346
+ path: releasePath(releaseId)
347
+ });
348
+ const url = new URL(uri);
349
+ return decodeURIComponent(url.pathname);
350
+ };
351
+ var removeRelease = async (releaseId) => {
352
+ await Filesystem.rmdir({
353
+ directory: Directory.Library,
354
+ path: releasePath(releaseId),
355
+ recursive: true
356
+ }).catch(() => {
357
+ return;
358
+ });
359
+ };
360
+ var createStore = () => {
361
+ let staging;
362
+ return {
363
+ abort: async (releaseId) => {
364
+ staging = undefined;
365
+ await removeRelease(releaseId);
366
+ const state = await readState();
367
+ if (state.readyRelease === releaseId || state.pendingRelease === releaseId)
368
+ await writeState({
369
+ activeRelease: state.activeRelease,
370
+ quarantinedReleases: state.quarantinedReleases,
371
+ recovery: state.recovery
372
+ });
373
+ },
374
+ activate: async (releaseId) => {
375
+ const state = await readState();
376
+ if (state.readyRelease !== releaseId)
377
+ throw new TypeError("Mobile update is not committed and ready to activate.");
378
+ const path = await releaseNativePath(releaseId);
379
+ const previousPath = await currentServerBasePath();
380
+ await writeState({
381
+ activeRelease: state.activeRelease,
382
+ pendingRelease: releaseId,
383
+ pendingStartedAt: Date.now(),
384
+ previousPath,
385
+ quarantinedReleases: state.quarantinedReleases
386
+ });
387
+ try {
388
+ await watchdog.arm({ releaseId });
389
+ webView().setServerBasePath(path);
390
+ } catch (error) {
391
+ await watchdog.confirm({ releaseId }).catch(() => {
392
+ return;
393
+ });
394
+ await removeRelease(releaseId);
395
+ await writeState({
396
+ activeRelease: state.activeRelease,
397
+ quarantinedReleases: state.quarantinedReleases
398
+ });
399
+ throw error;
400
+ }
401
+ },
402
+ begin: async (manifest) => {
403
+ await removeRelease(manifest.releaseId);
404
+ await Filesystem.mkdir({
405
+ directory: Directory.Library,
406
+ path: releasePath(manifest.releaseId),
407
+ recursive: true
408
+ });
409
+ staging = manifest;
410
+ },
411
+ commit: async (manifest) => {
412
+ if (staging?.releaseId !== manifest.releaseId)
413
+ throw new TypeError("Mobile update staging transaction changed identity.");
414
+ const state = await readState();
415
+ await writeState({
416
+ activeRelease: state.activeRelease,
417
+ quarantinedReleases: state.quarantinedReleases,
418
+ readyRelease: manifest.releaseId
419
+ });
420
+ staging = undefined;
421
+ },
422
+ write: async (file, contents) => {
423
+ if (!staging || !staging.files.some((candidate) => candidate.path === file.path))
424
+ throw new TypeError("Mobile update write is outside its staging transaction.");
425
+ await Filesystem.writeFile({
426
+ data: bytesBase64(contents),
427
+ directory: Directory.Library,
428
+ path: `${releasePath(staging.releaseId)}/${file.path}`,
429
+ recursive: true
430
+ });
431
+ }
432
+ };
433
+ };
434
+ var createVerifier = (publicKeys) => ({
435
+ digest: async (contents) => {
436
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", arrayBuffer(contents)));
437
+ return [...digest].map((byte) => byte.toString(16).padStart(2, "0")).join("");
438
+ },
439
+ verify: async (manifest) => {
440
+ const encoded = publicKeys[manifest.signature.keyId];
441
+ if (!encoded)
442
+ return false;
443
+ try {
444
+ const key = await crypto.subtle.importKey("spki", arrayBuffer(base64Bytes(encoded)), { name: "ECDSA", namedCurve: "P-256" }, false, ["verify"]);
445
+ return crypto.subtle.verify({ hash: "SHA-256", name: "ECDSA" }, key, arrayBuffer(base64Bytes(manifest.signature.value)), arrayBuffer(absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest))));
446
+ } catch {
447
+ return false;
448
+ }
449
+ }
450
+ });
451
+ var emitUpdateResult = (detail) => dispatchEvent(new CustomEvent("absolute:mobile-update", { detail }));
452
+ var removePriorRelease = async (prior, active) => {
453
+ if (prior && prior !== active)
454
+ await removeRelease(prior);
455
+ };
456
+ var reconcilePendingRelease = async (store, state) => {
457
+ if (!state.pendingRelease)
458
+ return state;
459
+ const pendingPath = await releaseNativePath(state.pendingRelease);
460
+ const currentPath = await currentServerBasePath();
461
+ if (currentPath !== pendingPath) {
462
+ const failed = state.pendingRelease;
463
+ await store.abort(failed);
464
+ emitUpdateResult({ kind: "rolled-back", releaseId: failed });
465
+ return readState();
466
+ }
467
+ webView().persistServerBasePath();
468
+ const next = { activeRelease: state.pendingRelease };
469
+ await writeState(next);
470
+ await watchdog.confirm({ releaseId: state.pendingRelease });
471
+ await removePriorRelease(state.activeRelease, next.activeRelease);
472
+ emitUpdateResult({ kind: "activated", releaseId: next.activeRelease });
473
+ return next;
474
+ };
475
+ var consumeNativeRecovery = async (state) => {
476
+ if (!state.recovery)
477
+ return state;
478
+ const { recovery, ...next } = state;
479
+ emitUpdateResult({
480
+ durationMs: Math.round(recovery.durationMs),
481
+ kind: "rolled-back",
482
+ reason: recovery.reason,
483
+ releaseId: recovery.releaseId
484
+ });
485
+ await writeState(next);
486
+ return next;
487
+ };
488
+ var installAbsoluteMobileShellUpdates = async (manifest) => {
489
+ if (!manifest.updates)
490
+ return;
491
+ const store = createStore();
492
+ const recovered = await consumeNativeRecovery(await readState());
493
+ const state = await reconcilePendingRelease(store, recovered);
494
+ const client = createAbsoluteMobileUpdateClient({
495
+ config: {
496
+ appId: manifest.appId,
497
+ blockedReleaseIds: state.quarantinedReleases ?? [],
498
+ channel: manifest.updates.channel,
499
+ currentReleaseId: state.activeRelease ?? `embedded:${manifest.appBuild}`,
500
+ installationId: await installationId(),
501
+ manifestUrl: manifest.updates.manifestUrl,
502
+ runtimeFingerprint: manifest.nativeRuntime
503
+ },
504
+ store,
505
+ verifier: createVerifier(manifest.updates.publicKeys)
506
+ });
507
+ const activateDownloaded = async (result) => {
508
+ if (result.kind !== "downloaded")
509
+ return;
510
+ emitUpdateResult({
511
+ kind: "downloaded",
512
+ releaseId: result.manifest.releaseId
513
+ });
514
+ await client.activate(result.manifest.releaseId);
515
+ };
516
+ client.download().then((result) => {
517
+ if (result.kind === "quarantined") {
518
+ emitUpdateResult({
519
+ kind: "quarantined",
520
+ releaseId: result.releaseId
521
+ });
522
+ return;
523
+ }
524
+ return activateDownloaded(result);
525
+ }).catch((error) => {
526
+ console.error("[Absolute Mobile] Update failed:", error);
527
+ emitUpdateResult({ kind: "failed" });
528
+ });
529
+ };
530
+ export {
531
+ installAbsoluteMobileShellUpdates
532
+ };