@absolutejs/absolute 0.20.0-beta.80 → 0.20.0-beta.81
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/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +1170 -222
- package/dist/build.js.map +7 -5
- package/dist/cli/{compile-x4hfth3p.js → compile-t73ac1zb.js} +2 -2
- package/dist/cli/{config-z8wwxatc.js → config-naw5fer4.js} +1 -1
- package/dist/cli/{dev-8vv459ty.js → dev-skb8dz33.js} +1 -1
- package/dist/cli/{index-w5y7wxn9.js → index-8gksmws3.js} +1 -1
- package/dist/cli/{index-6fb7k6dg.js → index-czw3jd8f.js} +47 -1
- package/dist/cli/{index-d4ar3mwd.js → index-q78x1k9q.js} +2 -2
- package/dist/cli/index.js +5 -5
- package/dist/cli/{mobile-mfp26v2d.js → mobile-rdwdcks1.js} +359 -136
- package/dist/cli/{start-dtrp1pry.js → start-jbgfw27x.js} +3 -3
- package/dist/index.js +2043 -1095
- package/dist/index.js.map +7 -5
- package/dist/mobile/index.js +946 -4
- package/dist/mobile/index.js.map +7 -5
- package/dist/mobile/remoteMacAgentEntry.js +205 -205
- package/dist/src/mobile/config.d.ts +8 -0
- package/dist/src/mobile/index.d.ts +1 -0
- package/dist/src/mobile/updateServer.d.ts +33 -0
- package/dist/types/build.d.ts +16 -0
- package/package.json +3 -2
package/dist/mobile/index.js
CHANGED
|
@@ -597,10 +597,14 @@ var init_startupBanner = __esm(() => {
|
|
|
597
597
|
});
|
|
598
598
|
|
|
599
599
|
// src/mobile/config.ts
|
|
600
|
+
var exports_config = {};
|
|
601
|
+
__export(exports_config, {
|
|
602
|
+
normalizeAbsoluteMobileConfig: () => normalizeAbsoluteMobileConfig
|
|
603
|
+
});
|
|
600
604
|
import { readFileSync as readFileSync2 } from "fs";
|
|
601
605
|
import { resolve as resolve6 } from "path";
|
|
602
606
|
import { createHash as createHash8, createPublicKey, X509Certificate as X509Certificate2 } from "crypto";
|
|
603
|
-
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000, MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000, MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field2) => {
|
|
607
|
+
var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FINGERPRINT_PATTERN, UPDATE_NAME_PATTERN, UPDATE_PUBLIC_KEY_PATTERN, ENVIRONMENT_NAME_PATTERN, DEFAULT_UPDATE_BOOT_TIMEOUT_MS = 20000, MINIMUM_UPDATE_BOOT_TIMEOUT_MS = 5000, MAXIMUM_UPDATE_BOOT_TIMEOUT_MS = 120000, HOSTNAME_PATTERN, EXPO_RESERVED_ROUTE_PREFIXES, resolveProjectPath = (projectRoot, value, field2) => {
|
|
604
608
|
const root = resolve6(projectRoot);
|
|
605
609
|
const path = resolve6(root, value);
|
|
606
610
|
if (path !== root && !path.startsWith(`${root}/`)) {
|
|
@@ -785,6 +789,48 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
785
789
|
publicKeys,
|
|
786
790
|
...expoCodeSigning ? { expoCodeSigning } : {}
|
|
787
791
|
};
|
|
792
|
+
}, normalizeUpdateServer = (config, productionOrigin, projectRoot, updates) => {
|
|
793
|
+
if (!updates || !config.updates)
|
|
794
|
+
return;
|
|
795
|
+
const registryModule = requireText(config.updates.server?.registry ?? "mobile.update.ts", "mobile.updates.server.registry");
|
|
796
|
+
resolveProjectPath(projectRoot, registryModule, "mobile.updates.server.registry");
|
|
797
|
+
const expoPrivateKeyEnv = requireText(config.updates.server?.expoPrivateKeyEnv ?? "ABSOLUTE_EXPO_UPDATE_PRIVATE_KEY", "mobile.updates.server.expoPrivateKeyEnv");
|
|
798
|
+
if (!ENVIRONMENT_NAME_PATTERN.test(expoPrivateKeyEnv))
|
|
799
|
+
throw new TypeError("mobile.updates.server.expoPrivateKeyEnv must be a valid environment variable name.");
|
|
800
|
+
const autoMount = config.updates.server?.autoMount ?? true;
|
|
801
|
+
if (autoMount) {
|
|
802
|
+
const manifest = new URL(updates.manifestUrl);
|
|
803
|
+
if (manifest.origin !== productionOrigin)
|
|
804
|
+
throw new TypeError("mobile.updates.manifestUrl must use mobile.server.productionOrigin while mobile.updates.server.autoMount is enabled.");
|
|
805
|
+
if (!manifest.pathname.endsWith("/update.json"))
|
|
806
|
+
throw new TypeError("mobile.updates.manifestUrl must end in /update.json while mobile.updates.server.autoMount is enabled.");
|
|
807
|
+
}
|
|
808
|
+
const expoCodeSigningKeys = {};
|
|
809
|
+
if (updates.expoCodeSigning)
|
|
810
|
+
expoCodeSigningKeys[updates.expoCodeSigning.keyId] = {
|
|
811
|
+
certificatePem: updates.expoCodeSigning.certificatePem,
|
|
812
|
+
privateKeyEnv: expoPrivateKeyEnv
|
|
813
|
+
};
|
|
814
|
+
for (const [keyId, key] of Object.entries(config.updates.server?.expoCodeSigningKeys ?? {}).sort(([left], [right]) => left.localeCompare(right))) {
|
|
815
|
+
if (!updates.expoCodeSigning)
|
|
816
|
+
throw new TypeError("mobile.updates.server.expoCodeSigningKeys requires the Expo engine and expoCodeSigning.");
|
|
817
|
+
if (!UPDATE_NAME_PATTERN.test(keyId))
|
|
818
|
+
throw new TypeError("mobile.updates.server.expoCodeSigningKeys contains an invalid key ID.");
|
|
819
|
+
if (expoCodeSigningKeys[keyId])
|
|
820
|
+
throw new TypeError(`mobile.updates.server.expoCodeSigningKeys.${keyId} duplicates the active Expo key.`);
|
|
821
|
+
const privateKeyEnv = requireText(key.privateKeyEnv, `mobile.updates.server.expoCodeSigningKeys.${keyId}.privateKeyEnv`);
|
|
822
|
+
if (!ENVIRONMENT_NAME_PATTERN.test(privateKeyEnv))
|
|
823
|
+
throw new TypeError(`mobile.updates.server.expoCodeSigningKeys.${keyId}.privateKeyEnv must be a valid environment variable name.`);
|
|
824
|
+
const certificatePath = resolveProjectPath(projectRoot, requireText(key.certificatePath, `mobile.updates.server.expoCodeSigningKeys.${keyId}.certificatePath`), `mobile.updates.server.expoCodeSigningKeys.${keyId}.certificatePath`);
|
|
825
|
+
const { certificate, certificatePem } = readExpoCodeSigningCertificate(certificatePath);
|
|
826
|
+
if (certificate.publicKey.asymmetricKeyType !== "rsa" || certificate.issuer !== certificate.subject || !certificate.verify(certificate.publicKey))
|
|
827
|
+
throw new TypeError(`mobile.updates.server.expoCodeSigningKeys.${keyId} certificate must be a self-signed RSA root.`);
|
|
828
|
+
const now = Date.now();
|
|
829
|
+
if (now < Date.parse(certificate.validFrom) || now > Date.parse(certificate.validTo))
|
|
830
|
+
throw new TypeError(`mobile.updates.server.expoCodeSigningKeys.${keyId} certificate is not currently valid.`);
|
|
831
|
+
expoCodeSigningKeys[keyId] = { certificatePem, privateKeyEnv };
|
|
832
|
+
}
|
|
833
|
+
return { autoMount, expoCodeSigningKeys, registryModule };
|
|
788
834
|
}, validateExpoNativeRouteSegment = (path, segment, index, count, parameters) => {
|
|
789
835
|
if (segment === "*" && (index !== count - 1 || count === 1)) {
|
|
790
836
|
throw new TypeError(`mobile.routes.native route ${path} must use * once, as the final segment after a static or parameterized prefix.`);
|
|
@@ -844,6 +890,7 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
844
890
|
throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
|
|
845
891
|
}
|
|
846
892
|
const updates = normalizeUpdates(config, productionOrigin, projectRoot);
|
|
893
|
+
const updateServer = normalizeUpdateServer(config, productionOrigin, projectRoot, updates);
|
|
847
894
|
const observability = normalizeObservability(config, productionOrigin);
|
|
848
895
|
return {
|
|
849
896
|
androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
|
|
@@ -863,7 +910,8 @@ var APP_ID_PATTERN, SCHEME_PATTERN, APPLE_APP_ID_PREFIX_PATTERN, CERTIFICATE_FIN
|
|
|
863
910
|
platforms: normalizePlatforms(config.platforms),
|
|
864
911
|
productionOrigin,
|
|
865
912
|
pushAndroidGoogleServicesFile: resolveProjectPath(projectRoot, config.pushNotifications?.android?.googleServicesFile ?? "google-services.json", "mobile.pushNotifications.android.googleServicesFile"),
|
|
866
|
-
...updates ? { updates } : {}
|
|
913
|
+
...updates ? { updates } : {},
|
|
914
|
+
...updateServer ? { updateServer } : {}
|
|
867
915
|
};
|
|
868
916
|
};
|
|
869
917
|
var init_config = __esm(() => {
|
|
@@ -873,6 +921,7 @@ var init_config = __esm(() => {
|
|
|
873
921
|
CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
|
|
874
922
|
UPDATE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u;
|
|
875
923
|
UPDATE_PUBLIC_KEY_PATTERN = /^[A-Za-z0-9+/]+={0,2}$/u;
|
|
924
|
+
ENVIRONMENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
876
925
|
HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
|
|
877
926
|
EXPO_RESERVED_ROUTE_PREFIXES = new Set([
|
|
878
927
|
"_expo",
|
|
@@ -19902,6 +19951,889 @@ var init_web_vitals_attribution = __esm(() => {
|
|
|
19902
19951
|
z = [800, 1800];
|
|
19903
19952
|
});
|
|
19904
19953
|
|
|
19954
|
+
// node_modules/@absolutejs/deploy/dist/mobileUpdate.js
|
|
19955
|
+
var exports_mobileUpdate = {};
|
|
19956
|
+
__export(exports_mobileUpdate, {
|
|
19957
|
+
MOBILE_UPDATE_REGISTRY_FORMAT: () => MOBILE_UPDATE_REGISTRY_FORMAT,
|
|
19958
|
+
MobileUpdateRegistryError: () => MobileUpdateRegistryError,
|
|
19959
|
+
createMobileUpdateHandler: () => createMobileUpdateHandler,
|
|
19960
|
+
createMobileUpdateRegistry: () => createMobileUpdateRegistry,
|
|
19961
|
+
parseMobileUpdateManifest: () => parseMobileUpdateManifest
|
|
19962
|
+
});
|
|
19963
|
+
import {
|
|
19964
|
+
createHash as createHash15,
|
|
19965
|
+
createPrivateKey,
|
|
19966
|
+
createPublicKey as createPublicKey2,
|
|
19967
|
+
sign as sign2,
|
|
19968
|
+
verify as verify2,
|
|
19969
|
+
X509Certificate as X509Certificate3
|
|
19970
|
+
} from "crypto";
|
|
19971
|
+
import { readFile as readFile23, stat as stat5 } from "fs/promises";
|
|
19972
|
+
import path from "path";
|
|
19973
|
+
var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updates", MAX_FILE_BYTES, MAX_TOTAL_BYTES, HASH, RELEASE, APP_ID, NAME, EXPO_DESCRIPTOR = "_absolute/expo-update.json", EXPO_CODE_SIGNING_ALGORITHM = "rsa-v1_5-sha256", MobileUpdateRegistryError, object6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), text2 = (value, field2) => {
|
|
19974
|
+
if (typeof value !== "string" || value.length === 0)
|
|
19975
|
+
throw new MobileUpdateRegistryError(`Mobile update ${field2} is invalid`);
|
|
19976
|
+
return value;
|
|
19977
|
+
}, iso = (value) => typeof value === "string" && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value, safePath2 = (value) => {
|
|
19978
|
+
const file = text2(value, "file path").replaceAll("\\", "/");
|
|
19979
|
+
if (file.startsWith("/") || file.split("/").some((segment) => !segment || segment === "." || segment === ".."))
|
|
19980
|
+
throw new MobileUpdateRegistryError("Mobile update file path is invalid");
|
|
19981
|
+
return file;
|
|
19982
|
+
}, expoAsset = (value) => {
|
|
19983
|
+
if (!object6(value))
|
|
19984
|
+
throw new MobileUpdateRegistryError("Expo update asset is invalid");
|
|
19985
|
+
const assetPath = safePath2(value.path);
|
|
19986
|
+
if (value.extension !== undefined && (typeof value.extension !== "string" || !/^[A-Za-z0-9]+$/.test(value.extension)))
|
|
19987
|
+
throw new MobileUpdateRegistryError("Expo update asset extension is invalid");
|
|
19988
|
+
return {
|
|
19989
|
+
...typeof value.extension === "string" ? { extension: value.extension } : {},
|
|
19990
|
+
path: assetPath
|
|
19991
|
+
};
|
|
19992
|
+
}, parseExpoUpdateDescriptor = (bytes) => {
|
|
19993
|
+
let value;
|
|
19994
|
+
try {
|
|
19995
|
+
value = JSON.parse(new TextDecoder().decode(bytes));
|
|
19996
|
+
} catch {
|
|
19997
|
+
throw new MobileUpdateRegistryError("Expo update descriptor is invalid");
|
|
19998
|
+
}
|
|
19999
|
+
if (!object6(value) || value.engine !== "expo" || value.format !== 1 || !object6(value.expoConfig) || !object6(value.platforms) || typeof value.runtimeVersion !== "string" || !HASH.test(value.runtimeVersion))
|
|
20000
|
+
throw new MobileUpdateRegistryError("Expo update descriptor is invalid");
|
|
20001
|
+
const platforms = {};
|
|
20002
|
+
for (const name of ["android", "ios"]) {
|
|
20003
|
+
const candidate = value.platforms[name];
|
|
20004
|
+
if (candidate === undefined)
|
|
20005
|
+
continue;
|
|
20006
|
+
if (!object6(candidate) || !Array.isArray(candidate.assets) || !object6(candidate.launchAsset))
|
|
20007
|
+
throw new MobileUpdateRegistryError("Expo update platform descriptor is invalid");
|
|
20008
|
+
platforms[name] = {
|
|
20009
|
+
assets: candidate.assets.map(expoAsset),
|
|
20010
|
+
launchAsset: expoAsset(candidate.launchAsset)
|
|
20011
|
+
};
|
|
20012
|
+
}
|
|
20013
|
+
if (Object.keys(platforms).length === 0)
|
|
20014
|
+
throw new MobileUpdateRegistryError("Expo update descriptor has no native platforms");
|
|
20015
|
+
return {
|
|
20016
|
+
engine: "expo",
|
|
20017
|
+
expoConfig: value.expoConfig,
|
|
20018
|
+
format: 1,
|
|
20019
|
+
platforms,
|
|
20020
|
+
runtimeVersion: value.runtimeVersion
|
|
20021
|
+
};
|
|
20022
|
+
}, parseMobileUpdateManifest = (value) => {
|
|
20023
|
+
if (!object6(value) || value.format !== 1)
|
|
20024
|
+
throw new MobileUpdateRegistryError("Mobile update manifest is invalid");
|
|
20025
|
+
const appId = text2(value.appId, "appId");
|
|
20026
|
+
const channel = text2(value.channel, "channel");
|
|
20027
|
+
const releaseId = text2(value.releaseId, "releaseId");
|
|
20028
|
+
const runtimeFingerprint = text2(value.runtimeFingerprint, "runtime");
|
|
20029
|
+
if (!APP_ID.test(appId) || !NAME.test(channel) || !RELEASE.test(releaseId))
|
|
20030
|
+
throw new MobileUpdateRegistryError("Mobile update identity is invalid");
|
|
20031
|
+
if (!HASH.test(runtimeFingerprint) || !iso(value.createdAt))
|
|
20032
|
+
throw new MobileUpdateRegistryError("Mobile update runtime or timestamp is invalid");
|
|
20033
|
+
if (value.classification !== "bug-fix" && value.classification !== "content" && value.classification !== "security")
|
|
20034
|
+
throw new MobileUpdateRegistryError("Mobile update classification is invalid");
|
|
20035
|
+
if (value.withinSubmittedPurpose !== true)
|
|
20036
|
+
throw new MobileUpdateRegistryError("Mobile update policy attestation is missing");
|
|
20037
|
+
if (!Array.isArray(value.files) || value.files.length === 0)
|
|
20038
|
+
throw new MobileUpdateRegistryError("Mobile update file inventory is invalid");
|
|
20039
|
+
const files = value.files.map((candidate) => {
|
|
20040
|
+
if (!object6(candidate))
|
|
20041
|
+
throw new MobileUpdateRegistryError("Mobile update file is invalid");
|
|
20042
|
+
const filePath = safePath2(candidate.path);
|
|
20043
|
+
if (!Number.isSafeInteger(candidate.bytes) || Number(candidate.bytes) < 0 || Number(candidate.bytes) > MAX_FILE_BYTES || typeof candidate.sha256 !== "string" || !HASH.test(candidate.sha256))
|
|
20044
|
+
throw new MobileUpdateRegistryError(`Mobile update file ${filePath} is invalid`);
|
|
20045
|
+
return {
|
|
20046
|
+
bytes: Number(candidate.bytes),
|
|
20047
|
+
path: filePath,
|
|
20048
|
+
sha256: candidate.sha256
|
|
20049
|
+
};
|
|
20050
|
+
});
|
|
20051
|
+
if (files.reduce((total, file) => total + file.bytes, 0) > MAX_TOTAL_BYTES || new Set(files.map((file) => file.path)).size !== files.length || files.some((file, index) => index > 0 ? file.path.localeCompare(files[index - 1]?.path ?? "") <= 0 : false))
|
|
20052
|
+
throw new MobileUpdateRegistryError("Mobile update file inventory is invalid");
|
|
20053
|
+
const signatureKeyId = object6(value.signature) ? text2(value.signature.keyId, "signature key") : "";
|
|
20054
|
+
const signatureValue = object6(value.signature) ? text2(value.signature.value, "signature") : "";
|
|
20055
|
+
if (!object6(value.signature) || value.signature.algorithm !== "ecdsa-p256-sha256" || !NAME.test(signatureKeyId) || !/^[A-Za-z0-9+/]+={0,2}$/.test(signatureValue) || Buffer.from(signatureValue, "base64").byteLength !== 64 || Buffer.from(signatureValue, "base64").toString("base64") !== signatureValue)
|
|
20056
|
+
throw new MobileUpdateRegistryError("Mobile update signature is invalid");
|
|
20057
|
+
return {
|
|
20058
|
+
appId,
|
|
20059
|
+
channel,
|
|
20060
|
+
classification: value.classification,
|
|
20061
|
+
createdAt: value.createdAt,
|
|
20062
|
+
files,
|
|
20063
|
+
format: 1,
|
|
20064
|
+
releaseId,
|
|
20065
|
+
runtimeFingerprint,
|
|
20066
|
+
signature: {
|
|
20067
|
+
algorithm: "ecdsa-p256-sha256",
|
|
20068
|
+
keyId: signatureKeyId,
|
|
20069
|
+
value: signatureValue
|
|
20070
|
+
},
|
|
20071
|
+
withinSubmittedPurpose: true
|
|
20072
|
+
};
|
|
20073
|
+
}, canonicalValue2 = (value) => {
|
|
20074
|
+
if (Array.isArray(value))
|
|
20075
|
+
return value.map(canonicalValue2);
|
|
20076
|
+
if (!object6(value))
|
|
20077
|
+
return value;
|
|
20078
|
+
return Object.fromEntries(Object.keys(value).sort().map((key) => [key, canonicalValue2(value[key])]));
|
|
20079
|
+
}, verifyManifestSignature = (manifest, publicKeys) => {
|
|
20080
|
+
const encoded = publicKeys[manifest.signature.keyId];
|
|
20081
|
+
if (!encoded)
|
|
20082
|
+
throw new MobileUpdateRegistryError("Mobile update signing key is not trusted");
|
|
20083
|
+
let publicKey;
|
|
20084
|
+
try {
|
|
20085
|
+
const der = Buffer.from(encoded, "base64");
|
|
20086
|
+
if (der.toString("base64") !== encoded)
|
|
20087
|
+
throw new Error("invalid base64");
|
|
20088
|
+
publicKey = createPublicKey2({ format: "der", key: der, type: "spki" });
|
|
20089
|
+
} catch {
|
|
20090
|
+
throw new MobileUpdateRegistryError("Mobile update trusted public key is invalid");
|
|
20091
|
+
}
|
|
20092
|
+
if (publicKey.asymmetricKeyType !== "ec" || publicKey.asymmetricKeyDetails?.namedCurve !== "prime256v1")
|
|
20093
|
+
throw new MobileUpdateRegistryError("Mobile update trusted public key must use ECDSA P-256");
|
|
20094
|
+
const { signature: _signature, ...unsigned } = manifest;
|
|
20095
|
+
if (!verify2("sha256", new TextEncoder().encode(JSON.stringify(canonicalValue2(unsigned))), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64")))
|
|
20096
|
+
throw new MobileUpdateRegistryError("Mobile update signature verification failed");
|
|
20097
|
+
}, parseChannel = (value) => {
|
|
20098
|
+
if (!object6(value) || value.format !== MOBILE_UPDATE_REGISTRY_FORMAT)
|
|
20099
|
+
throw new MobileUpdateRegistryError("Mobile update channel is invalid");
|
|
20100
|
+
const appId = text2(value.appId, "channel appId");
|
|
20101
|
+
const channel = text2(value.channel, "channel");
|
|
20102
|
+
if (!APP_ID.test(appId) || !NAME.test(channel) || !iso(value.promotedAt) || typeof value.rollout !== "number" || value.rollout < 0 || value.rollout > 1 || value.releaseId !== undefined && (typeof value.releaseId !== "string" || !RELEASE.test(value.releaseId)) || value.fallbackReleaseId !== undefined && (typeof value.fallbackReleaseId !== "string" || !RELEASE.test(value.fallbackReleaseId)) || value.activationId !== undefined && (typeof value.activationId !== "string" || !HASH.test(value.activationId)) || value.activatedAt !== undefined && !iso(value.activatedAt) || value.activationId === undefined !== (value.activatedAt === undefined))
|
|
20103
|
+
throw new MobileUpdateRegistryError("Mobile update channel is invalid");
|
|
20104
|
+
return {
|
|
20105
|
+
...value.activationId ? { activationId: value.activationId } : {},
|
|
20106
|
+
...value.activatedAt ? { activatedAt: value.activatedAt } : {},
|
|
20107
|
+
appId,
|
|
20108
|
+
channel,
|
|
20109
|
+
...value.fallbackReleaseId ? { fallbackReleaseId: value.fallbackReleaseId } : {},
|
|
20110
|
+
format: MOBILE_UPDATE_REGISTRY_FORMAT,
|
|
20111
|
+
promotedAt: value.promotedAt,
|
|
20112
|
+
...value.releaseId ? { releaseId: value.releaseId } : {},
|
|
20113
|
+
rollout: value.rollout
|
|
20114
|
+
};
|
|
20115
|
+
}, normalizedPrefix = (value) => {
|
|
20116
|
+
const prefix = value.replace(/^\/+|\/+$/g, "");
|
|
20117
|
+
if (!prefix || prefix.split("/").some((segment) => segment === "." || segment === ".."))
|
|
20118
|
+
throw new MobileUpdateRegistryError("Mobile update prefix is invalid");
|
|
20119
|
+
return prefix;
|
|
20120
|
+
}, appHash = (appId) => createHash15("sha256").update(appId).digest("hex"), digest = (bytes) => createHash15("sha256").update(bytes).digest("hex"), json = (value) => new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
|
|
20121
|
+
`), decode2 = (value) => JSON.parse(new TextDecoder().decode(value)), fileDigest = async (file) => {
|
|
20122
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
20123
|
+
for await (const chunk of file.stream())
|
|
20124
|
+
hasher.update(chunk);
|
|
20125
|
+
return hasher.digest("hex");
|
|
20126
|
+
}, rolloutMember = (input) => {
|
|
20127
|
+
if (input.rollout === 0)
|
|
20128
|
+
return false;
|
|
20129
|
+
if (input.rollout === 1)
|
|
20130
|
+
return true;
|
|
20131
|
+
const value = createHash15("sha256").update(`${input.appId}\x00${input.channel}\x00${input.releaseId}\x00${input.installationId}`).digest().readUInt32BE(0);
|
|
20132
|
+
return value / 4294967296 < input.rollout;
|
|
20133
|
+
}, createMobileUpdateRegistry = (options) => {
|
|
20134
|
+
const prefix = normalizedPrefix(options.prefix ?? DEFAULT_PREFIX);
|
|
20135
|
+
const clock = options.clock ?? (() => new Date);
|
|
20136
|
+
const root = (appId) => `${prefix}/${appHash(appId)}`;
|
|
20137
|
+
const releaseRoot = (manifest) => `${root(manifest.appId)}/releases/${manifest.releaseId}`;
|
|
20138
|
+
const manifestKey = (manifest) => `${releaseRoot(manifest)}/update.json`;
|
|
20139
|
+
const fileKey = (manifest, file) => `${releaseRoot(manifest)}/files/${file.path}`;
|
|
20140
|
+
const channelKey = (appId, channel) => {
|
|
20141
|
+
if (!APP_ID.test(appId) || !NAME.test(channel))
|
|
20142
|
+
throw new MobileUpdateRegistryError("Mobile update channel identity is invalid");
|
|
20143
|
+
return `${root(appId)}/channels/${channel}.json`;
|
|
20144
|
+
};
|
|
20145
|
+
const readManifest = async (appId, releaseId) => {
|
|
20146
|
+
const key = manifestKey({ appId, releaseId });
|
|
20147
|
+
const bytes = await options.store.get(key);
|
|
20148
|
+
if (!bytes)
|
|
20149
|
+
return null;
|
|
20150
|
+
const head = await options.store.head(key);
|
|
20151
|
+
if (!head || head.size !== bytes.byteLength || head.metadata?.sha256 !== digest(bytes))
|
|
20152
|
+
throw new MobileUpdateRegistryError("Stored mobile update manifest integrity failed");
|
|
20153
|
+
const manifest = parseMobileUpdateManifest(decode2(bytes));
|
|
20154
|
+
verifyManifestSignature(manifest, options.publicKeys);
|
|
20155
|
+
if (manifest.appId !== appId || manifest.releaseId !== releaseId)
|
|
20156
|
+
throw new MobileUpdateRegistryError("Stored mobile update identity changed");
|
|
20157
|
+
return { key, manifest };
|
|
20158
|
+
};
|
|
20159
|
+
const readChannel = async (appId, channel) => {
|
|
20160
|
+
const bytes = await options.store.get(channelKey(appId, channel));
|
|
20161
|
+
if (!bytes)
|
|
20162
|
+
return null;
|
|
20163
|
+
const value = parseChannel(decode2(bytes));
|
|
20164
|
+
if (value.appId !== appId || value.channel !== channel)
|
|
20165
|
+
throw new MobileUpdateRegistryError("Stored mobile update channel identity changed");
|
|
20166
|
+
return value;
|
|
20167
|
+
};
|
|
20168
|
+
const writeChannel = async (input, signal) => {
|
|
20169
|
+
const value = {
|
|
20170
|
+
...input,
|
|
20171
|
+
format: MOBILE_UPDATE_REGISTRY_FORMAT,
|
|
20172
|
+
promotedAt: clock().toISOString()
|
|
20173
|
+
};
|
|
20174
|
+
const bytes = json(value);
|
|
20175
|
+
await options.store.put(channelKey(value.appId, value.channel), bytes, {
|
|
20176
|
+
cacheControl: "no-cache",
|
|
20177
|
+
contentType: "application/json",
|
|
20178
|
+
maxBytes: bytes.byteLength,
|
|
20179
|
+
metadata: {
|
|
20180
|
+
channel: value.channel,
|
|
20181
|
+
...value.releaseId ? { releaseId: value.releaseId } : {},
|
|
20182
|
+
sha256: digest(bytes)
|
|
20183
|
+
},
|
|
20184
|
+
signal
|
|
20185
|
+
});
|
|
20186
|
+
return value;
|
|
20187
|
+
};
|
|
20188
|
+
const promoteUpdate = async (input) => {
|
|
20189
|
+
input.signal?.throwIfAborted();
|
|
20190
|
+
if (input.rollout <= 0 || input.rollout > 1)
|
|
20191
|
+
throw new MobileUpdateRegistryError("Mobile update rollout is invalid");
|
|
20192
|
+
const release = await readManifest(input.appId, input.releaseId);
|
|
20193
|
+
if (!release || release.manifest.channel !== input.channel)
|
|
20194
|
+
throw new MobileUpdateRegistryError("Mobile update was not published to this channel");
|
|
20195
|
+
const existing = await readChannel(input.appId, input.channel);
|
|
20196
|
+
await writeChannel({
|
|
20197
|
+
appId: input.appId,
|
|
20198
|
+
channel: input.channel,
|
|
20199
|
+
...existing?.releaseId && existing.releaseId !== input.releaseId ? { fallbackReleaseId: existing.releaseId } : existing?.fallbackReleaseId ? { fallbackReleaseId: existing.fallbackReleaseId } : {},
|
|
20200
|
+
releaseId: input.releaseId,
|
|
20201
|
+
rollout: input.rollout
|
|
20202
|
+
}, input.signal);
|
|
20203
|
+
return {
|
|
20204
|
+
appId: input.appId,
|
|
20205
|
+
channel: input.channel,
|
|
20206
|
+
releaseId: input.releaseId,
|
|
20207
|
+
rollout: input.rollout,
|
|
20208
|
+
stage: "promoted"
|
|
20209
|
+
};
|
|
20210
|
+
};
|
|
20211
|
+
const resolveUpdateState = async (input) => {
|
|
20212
|
+
const channel = await readChannel(input.appId, input.channel);
|
|
20213
|
+
if (!channel?.releaseId)
|
|
20214
|
+
return { status: "empty" };
|
|
20215
|
+
const selected = rolloutMember({
|
|
20216
|
+
appId: input.appId,
|
|
20217
|
+
channel: input.channel,
|
|
20218
|
+
installationId: input.installationId,
|
|
20219
|
+
releaseId: channel.releaseId,
|
|
20220
|
+
rollout: channel.rollout
|
|
20221
|
+
}) ? channel.releaseId : channel.fallbackReleaseId;
|
|
20222
|
+
if (!selected)
|
|
20223
|
+
return { status: "empty" };
|
|
20224
|
+
const release = await readManifest(input.appId, selected);
|
|
20225
|
+
if (!release || release.manifest.runtimeFingerprint !== input.runtimeFingerprint)
|
|
20226
|
+
return { status: "incompatible" };
|
|
20227
|
+
return {
|
|
20228
|
+
...selected === channel.releaseId && channel.activationId && channel.activatedAt ? {
|
|
20229
|
+
activationId: channel.activationId,
|
|
20230
|
+
activatedAt: channel.activatedAt
|
|
20231
|
+
} : {},
|
|
20232
|
+
manifest: release.manifest,
|
|
20233
|
+
manifestKey: release.key,
|
|
20234
|
+
status: "selected"
|
|
20235
|
+
};
|
|
20236
|
+
};
|
|
20237
|
+
return {
|
|
20238
|
+
publishUpdate: async (input) => {
|
|
20239
|
+
input.signal?.throwIfAborted();
|
|
20240
|
+
const manifest = parseMobileUpdateManifest(input.manifest);
|
|
20241
|
+
verifyManifestSignature(manifest, options.publicKeys);
|
|
20242
|
+
const localRoot = path.resolve(input.releaseDirectory);
|
|
20243
|
+
const localManifest = parseMobileUpdateManifest(JSON.parse(await readFile23(path.join(localRoot, "update.json"), "utf8")));
|
|
20244
|
+
if (JSON.stringify(localManifest) !== JSON.stringify(manifest))
|
|
20245
|
+
throw new MobileUpdateRegistryError("Local mobile update manifest changed");
|
|
20246
|
+
const existing = await readManifest(manifest.appId, manifest.releaseId);
|
|
20247
|
+
let reused = existing !== null;
|
|
20248
|
+
if (existing && JSON.stringify(existing.manifest) !== JSON.stringify(manifest))
|
|
20249
|
+
throw new MobileUpdateRegistryError("Published mobile update is immutable");
|
|
20250
|
+
if (!existing) {
|
|
20251
|
+
for (const file of manifest.files) {
|
|
20252
|
+
const local = path.join(localRoot, "files", file.path);
|
|
20253
|
+
const metadata = await stat5(local).catch(() => null);
|
|
20254
|
+
if (!metadata?.isFile() || metadata.size !== file.bytes)
|
|
20255
|
+
throw new MobileUpdateRegistryError(`Mobile update file ${file.path} size changed`);
|
|
20256
|
+
if (await fileDigest(Bun.file(local)) !== file.sha256)
|
|
20257
|
+
throw new MobileUpdateRegistryError(`Mobile update file ${file.path} integrity failed`);
|
|
20258
|
+
const key = fileKey(manifest, file);
|
|
20259
|
+
const stored = await options.store.head(key);
|
|
20260
|
+
if (!stored) {
|
|
20261
|
+
await options.store.put(key, Bun.file(local).stream(), {
|
|
20262
|
+
cacheControl: "public, max-age=31536000, immutable",
|
|
20263
|
+
contentType: "application/octet-stream",
|
|
20264
|
+
maxBytes: file.bytes,
|
|
20265
|
+
metadata: { releaseId: manifest.releaseId, sha256: file.sha256 },
|
|
20266
|
+
signal: input.signal
|
|
20267
|
+
});
|
|
20268
|
+
} else if (stored.size !== file.bytes || stored.metadata?.sha256 !== file.sha256 || stored.metadata?.releaseId !== manifest.releaseId)
|
|
20269
|
+
throw new MobileUpdateRegistryError("Stored mobile update file identity changed");
|
|
20270
|
+
}
|
|
20271
|
+
const bytes = json(manifest);
|
|
20272
|
+
await options.store.put(manifestKey(manifest), bytes, {
|
|
20273
|
+
cacheControl: "public, max-age=31536000, immutable",
|
|
20274
|
+
contentType: "application/json",
|
|
20275
|
+
maxBytes: bytes.byteLength,
|
|
20276
|
+
metadata: { releaseId: manifest.releaseId, sha256: digest(bytes) },
|
|
20277
|
+
signal: input.signal
|
|
20278
|
+
});
|
|
20279
|
+
if (!await readManifest(manifest.appId, manifest.releaseId))
|
|
20280
|
+
throw new MobileUpdateRegistryError("Mobile update publication verification failed");
|
|
20281
|
+
reused = false;
|
|
20282
|
+
}
|
|
20283
|
+
await promoteUpdate({
|
|
20284
|
+
appId: manifest.appId,
|
|
20285
|
+
channel: manifest.channel,
|
|
20286
|
+
releaseId: manifest.releaseId,
|
|
20287
|
+
rollout: input.rollout,
|
|
20288
|
+
signal: input.signal
|
|
20289
|
+
});
|
|
20290
|
+
return {
|
|
20291
|
+
appId: manifest.appId,
|
|
20292
|
+
channel: manifest.channel,
|
|
20293
|
+
releaseId: manifest.releaseId,
|
|
20294
|
+
reused,
|
|
20295
|
+
rollout: input.rollout,
|
|
20296
|
+
stage: "published"
|
|
20297
|
+
};
|
|
20298
|
+
},
|
|
20299
|
+
promoteUpdate,
|
|
20300
|
+
rollbackUpdate: async (input) => {
|
|
20301
|
+
input.signal?.throwIfAborted();
|
|
20302
|
+
const existing = await readChannel(input.appId, input.channel);
|
|
20303
|
+
if (!existing)
|
|
20304
|
+
throw new MobileUpdateRegistryError("Mobile update channel does not exist");
|
|
20305
|
+
if (input.releaseId) {
|
|
20306
|
+
const release = await readManifest(input.appId, input.releaseId);
|
|
20307
|
+
if (!release || release.manifest.channel !== input.channel)
|
|
20308
|
+
throw new MobileUpdateRegistryError("Mobile rollback release was not published");
|
|
20309
|
+
}
|
|
20310
|
+
const activatedAt = clock().toISOString();
|
|
20311
|
+
await writeChannel({
|
|
20312
|
+
...input.releaseId ? {
|
|
20313
|
+
activationId: digest(new TextEncoder().encode(`${input.appId}\x00${input.channel}\x00${input.releaseId}\x00${activatedAt}`)),
|
|
20314
|
+
activatedAt
|
|
20315
|
+
} : {},
|
|
20316
|
+
appId: input.appId,
|
|
20317
|
+
channel: input.channel,
|
|
20318
|
+
...existing.releaseId ? { fallbackReleaseId: existing.releaseId } : {},
|
|
20319
|
+
...input.releaseId ? { releaseId: input.releaseId } : {},
|
|
20320
|
+
rollout: input.releaseId ? 1 : 0
|
|
20321
|
+
}, input.signal);
|
|
20322
|
+
return {
|
|
20323
|
+
appId: input.appId,
|
|
20324
|
+
channel: input.channel,
|
|
20325
|
+
...input.releaseId ? { releaseId: input.releaseId } : {},
|
|
20326
|
+
stage: "rolled-back"
|
|
20327
|
+
};
|
|
20328
|
+
},
|
|
20329
|
+
resolveUpdate: async (input) => {
|
|
20330
|
+
const resolution = await resolveUpdateState(input);
|
|
20331
|
+
return resolution.status === "selected" ? { manifest: resolution.manifest, manifestKey: resolution.manifestKey } : null;
|
|
20332
|
+
},
|
|
20333
|
+
resolveUpdateState,
|
|
20334
|
+
readUpdateFile: async (input) => {
|
|
20335
|
+
const release = await readManifest(input.appId, input.releaseId);
|
|
20336
|
+
if (!release)
|
|
20337
|
+
return null;
|
|
20338
|
+
const requested = safePath2(input.path);
|
|
20339
|
+
const file = release.manifest.files.find((candidate) => candidate.path === requested);
|
|
20340
|
+
if (!file)
|
|
20341
|
+
return null;
|
|
20342
|
+
const key = fileKey(release.manifest, file);
|
|
20343
|
+
const [bytes, head] = await Promise.all([
|
|
20344
|
+
options.store.get(key),
|
|
20345
|
+
options.store.head(key)
|
|
20346
|
+
]);
|
|
20347
|
+
if (!bytes || !head)
|
|
20348
|
+
return null;
|
|
20349
|
+
if (bytes.byteLength !== file.bytes || head.size !== file.bytes || head.metadata?.sha256 !== file.sha256 || head.metadata?.releaseId !== release.manifest.releaseId || digest(bytes) !== file.sha256)
|
|
20350
|
+
throw new MobileUpdateRegistryError("Stored mobile update file integrity failed");
|
|
20351
|
+
return { bytes, file };
|
|
20352
|
+
}
|
|
20353
|
+
};
|
|
20354
|
+
}, expoUpdateId = (releaseId) => {
|
|
20355
|
+
const hash = RELEASE.test(releaseId) ? releaseId.slice("amu_".length, "amu_".length + 32) : createHash15("sha256").update(releaseId).digest("hex").slice(0, 32);
|
|
20356
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20)}`;
|
|
20357
|
+
}, expoContentType = (extension2, launch) => {
|
|
20358
|
+
if (launch)
|
|
20359
|
+
return "application/javascript";
|
|
20360
|
+
const normalized = extension2?.toLowerCase();
|
|
20361
|
+
if (normalized === "png")
|
|
20362
|
+
return "image/png";
|
|
20363
|
+
if (normalized === "jpg" || normalized === "jpeg")
|
|
20364
|
+
return "image/jpeg";
|
|
20365
|
+
if (normalized === "webp")
|
|
20366
|
+
return "image/webp";
|
|
20367
|
+
if (normalized === "gif")
|
|
20368
|
+
return "image/gif";
|
|
20369
|
+
if (normalized === "svg")
|
|
20370
|
+
return "image/svg+xml";
|
|
20371
|
+
if (normalized === "json")
|
|
20372
|
+
return "application/json";
|
|
20373
|
+
if (normalized === "ttf")
|
|
20374
|
+
return "font/ttf";
|
|
20375
|
+
if (normalized === "otf")
|
|
20376
|
+
return "font/otf";
|
|
20377
|
+
if (normalized === "woff")
|
|
20378
|
+
return "font/woff";
|
|
20379
|
+
if (normalized === "woff2")
|
|
20380
|
+
return "font/woff2";
|
|
20381
|
+
return "application/octet-stream";
|
|
20382
|
+
}, encodeUpdatePath = (value) => value.split("/").map(encodeURIComponent).join("/"), expoProtocolHeaders, resolveExpoCodeSigning = (value) => {
|
|
20383
|
+
if (!value)
|
|
20384
|
+
return;
|
|
20385
|
+
const entries = Object.entries(value.keys);
|
|
20386
|
+
if (entries.length === 0)
|
|
20387
|
+
throw new MobileUpdateRegistryError("Expo code signing requires at least one key");
|
|
20388
|
+
return new Map(entries.map(([keyId, material]) => {
|
|
20389
|
+
if (!NAME.test(keyId))
|
|
20390
|
+
throw new MobileUpdateRegistryError("Expo code-signing key ID is invalid");
|
|
20391
|
+
let certificate;
|
|
20392
|
+
let privateKey;
|
|
20393
|
+
try {
|
|
20394
|
+
certificate = new X509Certificate3(material.certificate);
|
|
20395
|
+
privateKey = createPrivateKey(material.privateKey);
|
|
20396
|
+
} catch (error) {
|
|
20397
|
+
throw new MobileUpdateRegistryError("Expo code-signing certificate or private key is invalid", { cause: error });
|
|
20398
|
+
}
|
|
20399
|
+
if (certificate.publicKey.asymmetricKeyType !== "rsa" || privateKey.asymmetricKeyType !== "rsa" || certificate.issuer !== certificate.subject || !certificate.verify(certificate.publicKey))
|
|
20400
|
+
throw new MobileUpdateRegistryError("Expo code signing requires a self-signed RSA root and private key");
|
|
20401
|
+
const certificatePublicKey = certificate.publicKey.export({
|
|
20402
|
+
format: "der",
|
|
20403
|
+
type: "spki"
|
|
20404
|
+
});
|
|
20405
|
+
const privatePublicKey = createPublicKey2(privateKey).export({
|
|
20406
|
+
format: "der",
|
|
20407
|
+
type: "spki"
|
|
20408
|
+
});
|
|
20409
|
+
if (!certificatePublicKey.equals(privatePublicKey))
|
|
20410
|
+
throw new MobileUpdateRegistryError("Expo code-signing private key does not match its certificate");
|
|
20411
|
+
const now = Date.now();
|
|
20412
|
+
if (now < Date.parse(certificate.validFrom) || now > Date.parse(certificate.validTo))
|
|
20413
|
+
throw new MobileUpdateRegistryError("Expo code-signing certificate is not currently valid");
|
|
20414
|
+
return [keyId, { keyId, privateKey }];
|
|
20415
|
+
}));
|
|
20416
|
+
}, expoSignatureExpectation = (value) => {
|
|
20417
|
+
const fields = new Map;
|
|
20418
|
+
for (const raw of value.split(",")) {
|
|
20419
|
+
const match = /^\s*([a-z][a-z0-9_-]*)(?:=(\?1|\?0|"[^"\\]*"|[a-z0-9._-]+))?\s*$/u.exec(raw);
|
|
20420
|
+
if (!match || fields.has(match[1]))
|
|
20421
|
+
throw new MobileUpdateRegistryError("Expo code-signing expectation is invalid");
|
|
20422
|
+
const encoded = match[2];
|
|
20423
|
+
fields.set(match[1], encoded === undefined || encoded === "?1" ? true : encoded.startsWith('"') ? encoded.slice(1, -1) : encoded);
|
|
20424
|
+
}
|
|
20425
|
+
return fields;
|
|
20426
|
+
}, expoExtraParam = (request, key) => {
|
|
20427
|
+
const value = request.headers.get("expo-extra-params");
|
|
20428
|
+
if (!value)
|
|
20429
|
+
return null;
|
|
20430
|
+
for (const raw of value.split(",")) {
|
|
20431
|
+
const match = /^\s*([a-z][a-z0-9_.*-]*)=(?:"((?:[^"\\]|\\["\\])*)"|([^\s,]+))\s*$/u.exec(raw);
|
|
20432
|
+
if (!match || match[1] !== key)
|
|
20433
|
+
continue;
|
|
20434
|
+
return match[2] !== undefined ? match[2].replace(/\\(["\\])/gu, "$1") : match[3] ?? null;
|
|
20435
|
+
}
|
|
20436
|
+
return null;
|
|
20437
|
+
}, requestedExpoSignature = (request, codeSigning) => {
|
|
20438
|
+
const expectation = request.headers.get("expo-expect-signature");
|
|
20439
|
+
if (!expectation)
|
|
20440
|
+
return;
|
|
20441
|
+
if (!codeSigning)
|
|
20442
|
+
throw new MobileUpdateRegistryError("Expo end-to-end code signing is not configured");
|
|
20443
|
+
const fields = expoSignatureExpectation(expectation);
|
|
20444
|
+
const keyId = fields.get("keyid");
|
|
20445
|
+
const algorithm = fields.get("alg");
|
|
20446
|
+
const signatureRequested = fields.get("sig") === true;
|
|
20447
|
+
if (!signatureRequested || keyId !== undefined && typeof keyId !== "string" || algorithm !== undefined && algorithm !== EXPO_CODE_SIGNING_ALGORITHM)
|
|
20448
|
+
throw new MobileUpdateRegistryError("Requested Expo code-signing parameters are unsupported");
|
|
20449
|
+
const selected = typeof keyId === "string" ? codeSigning.get(keyId) : codeSigning.size === 1 ? codeSigning.values().next().value : undefined;
|
|
20450
|
+
if (!selected)
|
|
20451
|
+
throw new MobileUpdateRegistryError("Requested Expo code-signing key is unavailable");
|
|
20452
|
+
return selected;
|
|
20453
|
+
}, expoSignatureHeader = (body, codeSigning) => {
|
|
20454
|
+
const signature = sign2("RSA-SHA256", Buffer.from(body), codeSigning.privateKey);
|
|
20455
|
+
return `sig="${signature.toString("base64")}", keyid="${codeSigning.keyId}", alg="${EXPO_CODE_SIGNING_ALGORITHM}"`;
|
|
20456
|
+
}, expoRollbackResponse = (request, codeSigning) => {
|
|
20457
|
+
const current = request.headers.get("expo-current-update-id");
|
|
20458
|
+
const embedded = request.headers.get("expo-embedded-update-id");
|
|
20459
|
+
if (!current || current === embedded)
|
|
20460
|
+
return new Response(null, { status: 204 });
|
|
20461
|
+
const boundary = `absolutejs-${crypto.randomUUID()}`;
|
|
20462
|
+
const directive = JSON.stringify({
|
|
20463
|
+
parameters: { commitTime: new Date().toISOString() },
|
|
20464
|
+
type: "rollBackToEmbedded"
|
|
20465
|
+
});
|
|
20466
|
+
const body = [
|
|
20467
|
+
`--${boundary}`,
|
|
20468
|
+
'content-disposition: form-data; name="directive"',
|
|
20469
|
+
"content-type: application/json; charset=utf-8",
|
|
20470
|
+
...codeSigning ? [`expo-signature: ${expoSignatureHeader(directive, codeSigning)}`] : [],
|
|
20471
|
+
"",
|
|
20472
|
+
directive,
|
|
20473
|
+
`--${boundary}--`,
|
|
20474
|
+
""
|
|
20475
|
+
].join(`\r
|
|
20476
|
+
`);
|
|
20477
|
+
return new Response(body, {
|
|
20478
|
+
headers: {
|
|
20479
|
+
...expoProtocolHeaders,
|
|
20480
|
+
"content-type": `multipart/mixed; boundary=${boundary}`
|
|
20481
|
+
}
|
|
20482
|
+
});
|
|
20483
|
+
}, createMobileUpdateHandler = (options) => {
|
|
20484
|
+
const route = (options.route ?? `/__absolute/mobile/updates/${options.channel}`).replace(/^\/+|\/+$/g, "");
|
|
20485
|
+
const allowedOrigins = new Set(options.allowedOrigins ?? ["capacitor://localhost", "https://localhost"]);
|
|
20486
|
+
const expoCodeSigning = resolveExpoCodeSigning(options.expoCodeSigning);
|
|
20487
|
+
return async (request) => {
|
|
20488
|
+
const origin = request.headers.get("origin");
|
|
20489
|
+
const cors = origin && allowedOrigins.has(origin) ? { "access-control-allow-origin": origin, vary: "Origin" } : {};
|
|
20490
|
+
if (request.method === "OPTIONS") {
|
|
20491
|
+
if (!origin || !allowedOrigins.has(origin))
|
|
20492
|
+
return new Response(null, { status: 403 });
|
|
20493
|
+
return new Response(null, {
|
|
20494
|
+
headers: {
|
|
20495
|
+
...cors,
|
|
20496
|
+
"access-control-allow-headers": "x-absolute-mobile-app,x-absolute-mobile-channel,x-absolute-mobile-installation,x-absolute-mobile-release,x-absolute-mobile-runtime",
|
|
20497
|
+
"access-control-allow-methods": "GET,OPTIONS",
|
|
20498
|
+
"access-control-max-age": "600"
|
|
20499
|
+
},
|
|
20500
|
+
status: 204
|
|
20501
|
+
});
|
|
20502
|
+
}
|
|
20503
|
+
if (request.method !== "GET")
|
|
20504
|
+
return new Response(null, { status: 405 });
|
|
20505
|
+
const pathname = new URL(request.url).pathname.replace(/^\/+/, "");
|
|
20506
|
+
const relative17 = pathname.startsWith(`${route}/`) ? pathname.slice(route.length + 1) : "";
|
|
20507
|
+
if (relative17 === "update.json") {
|
|
20508
|
+
const expoProtocolVersion = request.headers.get("expo-protocol-version");
|
|
20509
|
+
const expoProtocol = expoProtocolVersion !== null;
|
|
20510
|
+
if (expoProtocol && expoProtocolVersion !== "1")
|
|
20511
|
+
return Response.json({
|
|
20512
|
+
error: `Unsupported Expo Updates protocol version: ${expoProtocolVersion}`
|
|
20513
|
+
}, { status: 406 });
|
|
20514
|
+
const appId = request.headers.get("x-absolute-mobile-app");
|
|
20515
|
+
const channel = request.headers.get("x-absolute-mobile-channel");
|
|
20516
|
+
const installationId = expoProtocol ? expoExtraParam(request, "absolute-installation") ?? request.headers.get("x-absolute-mobile-installation") : request.headers.get("x-absolute-mobile-installation");
|
|
20517
|
+
const runtimeFingerprint = expoProtocol ? request.headers.get("expo-runtime-version") : request.headers.get("x-absolute-mobile-runtime");
|
|
20518
|
+
if (appId !== options.appId || channel !== options.channel || !installationId || !runtimeFingerprint)
|
|
20519
|
+
return new Response(null, { status: 400 });
|
|
20520
|
+
const resolution = options.registry.resolveUpdateState ? await options.registry.resolveUpdateState({
|
|
20521
|
+
appId,
|
|
20522
|
+
channel,
|
|
20523
|
+
installationId,
|
|
20524
|
+
runtimeFingerprint
|
|
20525
|
+
}) : undefined;
|
|
20526
|
+
const selected = resolution ? resolution.status === "selected" ? resolution : null : await options.registry.resolveUpdate({
|
|
20527
|
+
appId,
|
|
20528
|
+
channel,
|
|
20529
|
+
installationId,
|
|
20530
|
+
runtimeFingerprint
|
|
20531
|
+
});
|
|
20532
|
+
if (expoProtocol) {
|
|
20533
|
+
let requestedCodeSigning;
|
|
20534
|
+
try {
|
|
20535
|
+
requestedCodeSigning = requestedExpoSignature(request, expoCodeSigning);
|
|
20536
|
+
} catch (error) {
|
|
20537
|
+
return Response.json({
|
|
20538
|
+
error: error instanceof Error ? error.message : "Expo code-signing negotiation failed"
|
|
20539
|
+
}, { status: expoCodeSigning ? 406 : 400 });
|
|
20540
|
+
}
|
|
20541
|
+
if (!selected) {
|
|
20542
|
+
if (resolution?.status === "incompatible")
|
|
20543
|
+
return new Response(null, { status: 204 });
|
|
20544
|
+
return expoRollbackResponse(request, requestedCodeSigning);
|
|
20545
|
+
}
|
|
20546
|
+
const platform5 = request.headers.get("expo-platform");
|
|
20547
|
+
if (platform5 !== "android" && platform5 !== "ios")
|
|
20548
|
+
return new Response(null, { status: 400 });
|
|
20549
|
+
const updateId = expoUpdateId((resolution?.status === "selected" ? resolution.activationId : undefined) ?? selected.manifest.releaseId);
|
|
20550
|
+
if (request.headers.get("expo-current-update-id") === updateId)
|
|
20551
|
+
return new Response(null, { status: 204 });
|
|
20552
|
+
const descriptorFile = await options.registry.readUpdateFile({
|
|
20553
|
+
appId,
|
|
20554
|
+
path: EXPO_DESCRIPTOR,
|
|
20555
|
+
releaseId: selected.manifest.releaseId
|
|
20556
|
+
});
|
|
20557
|
+
if (!descriptorFile)
|
|
20558
|
+
return new Response(null, { status: 409 });
|
|
20559
|
+
const descriptor = parseExpoUpdateDescriptor(descriptorFile.bytes);
|
|
20560
|
+
const platformUpdate = descriptor.platforms[platform5];
|
|
20561
|
+
if (!platformUpdate || descriptor.runtimeVersion !== runtimeFingerprint)
|
|
20562
|
+
return new Response(null, { status: 204 });
|
|
20563
|
+
const origin2 = new URL(request.url).origin;
|
|
20564
|
+
const releaseRoute = `${origin2}/${route}/${selected.manifest.releaseId}/files`;
|
|
20565
|
+
const protocolAsset = (asset2, launch = false) => {
|
|
20566
|
+
const file2 = selected.manifest.files.find((candidate) => candidate.path === asset2.path);
|
|
20567
|
+
if (!file2)
|
|
20568
|
+
throw new MobileUpdateRegistryError(`Expo update references missing signed asset ${asset2.path}`);
|
|
20569
|
+
return {
|
|
20570
|
+
contentType: expoContentType(asset2.extension, launch),
|
|
20571
|
+
...asset2.extension ? { fileExtension: `.${asset2.extension}` } : {},
|
|
20572
|
+
key: file2.sha256,
|
|
20573
|
+
hash: Buffer.from(file2.sha256, "hex").toString("base64url"),
|
|
20574
|
+
url: `${releaseRoute}/${encodeUpdatePath(asset2.path)}`
|
|
20575
|
+
};
|
|
20576
|
+
};
|
|
20577
|
+
const manifest = JSON.stringify({
|
|
20578
|
+
assets: platformUpdate.assets.map((asset2) => protocolAsset(asset2)),
|
|
20579
|
+
createdAt: (resolution?.status === "selected" ? resolution.activatedAt : undefined) ?? selected.manifest.createdAt,
|
|
20580
|
+
extra: {
|
|
20581
|
+
absolutejs: {
|
|
20582
|
+
channel: selected.manifest.channel,
|
|
20583
|
+
releaseId: selected.manifest.releaseId
|
|
20584
|
+
},
|
|
20585
|
+
expoClient: descriptor.expoConfig
|
|
20586
|
+
},
|
|
20587
|
+
id: updateId,
|
|
20588
|
+
launchAsset: protocolAsset(platformUpdate.launchAsset, true),
|
|
20589
|
+
metadata: {
|
|
20590
|
+
channel: selected.manifest.channel,
|
|
20591
|
+
releaseId: selected.manifest.releaseId
|
|
20592
|
+
},
|
|
20593
|
+
runtimeVersion: descriptor.runtimeVersion
|
|
20594
|
+
});
|
|
20595
|
+
return new Response(manifest, {
|
|
20596
|
+
headers: {
|
|
20597
|
+
...expoProtocolHeaders,
|
|
20598
|
+
...requestedCodeSigning ? {
|
|
20599
|
+
"expo-signature": expoSignatureHeader(manifest, requestedCodeSigning)
|
|
20600
|
+
} : {}
|
|
20601
|
+
}
|
|
20602
|
+
});
|
|
20603
|
+
}
|
|
20604
|
+
if (!selected)
|
|
20605
|
+
return new Response(null, { status: 204 });
|
|
20606
|
+
return Response.json(selected.manifest, {
|
|
20607
|
+
headers: {
|
|
20608
|
+
...cors,
|
|
20609
|
+
"cache-control": "no-store",
|
|
20610
|
+
etag: `"${selected.manifest.releaseId}"`
|
|
20611
|
+
}
|
|
20612
|
+
});
|
|
20613
|
+
}
|
|
20614
|
+
const match = /^(amu_[a-f0-9]{64})\/files\/(.+)$/.exec(relative17);
|
|
20615
|
+
if (!match?.[1] || !match[2])
|
|
20616
|
+
return new Response(null, { status: 404 });
|
|
20617
|
+
const file = await options.registry.readUpdateFile({
|
|
20618
|
+
appId: options.appId,
|
|
20619
|
+
path: decodeURIComponent(match[2]),
|
|
20620
|
+
releaseId: match[1]
|
|
20621
|
+
});
|
|
20622
|
+
if (!file)
|
|
20623
|
+
return new Response(null, { status: 404 });
|
|
20624
|
+
return new Response(new Blob([new Uint8Array(file.bytes).buffer]), {
|
|
20625
|
+
headers: {
|
|
20626
|
+
...cors,
|
|
20627
|
+
"cache-control": "public, max-age=31536000, immutable",
|
|
20628
|
+
"content-length": String(file.file.bytes),
|
|
20629
|
+
"content-type": expoContentType(file.file.path.includes(".") ? file.file.path.slice(file.file.path.lastIndexOf(".") + 1) : undefined, false),
|
|
20630
|
+
etag: `"${file.file.sha256}"`
|
|
20631
|
+
}
|
|
20632
|
+
});
|
|
20633
|
+
};
|
|
20634
|
+
};
|
|
20635
|
+
var init_mobileUpdate = __esm(() => {
|
|
20636
|
+
MAX_FILE_BYTES = 32 * 1024 * 1024;
|
|
20637
|
+
MAX_TOTAL_BYTES = 128 * 1024 * 1024;
|
|
20638
|
+
HASH = /^[a-f0-9]{64}$/;
|
|
20639
|
+
RELEASE = /^amu_[a-f0-9]{64}$/;
|
|
20640
|
+
APP_ID = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
|
|
20641
|
+
NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
20642
|
+
MobileUpdateRegistryError = class MobileUpdateRegistryError extends Error {
|
|
20643
|
+
};
|
|
20644
|
+
expoProtocolHeaders = {
|
|
20645
|
+
"cache-control": "private, max-age=0",
|
|
20646
|
+
"content-type": "application/expo+json",
|
|
20647
|
+
"expo-protocol-version": "1",
|
|
20648
|
+
"expo-sfv-version": "0"
|
|
20649
|
+
};
|
|
20650
|
+
});
|
|
20651
|
+
|
|
20652
|
+
// src/mobile/updateServer.ts
|
|
20653
|
+
var exports_updateServer = {};
|
|
20654
|
+
__export(exports_updateServer, {
|
|
20655
|
+
ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT: () => ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT,
|
|
20656
|
+
DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE: () => DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE,
|
|
20657
|
+
createAbsoluteMobileUpdateServerPlugin: () => createAbsoluteMobileUpdateServerPlugin,
|
|
20658
|
+
inspectAbsoluteMobileUpdateServer: () => inspectAbsoluteMobileUpdateServer,
|
|
20659
|
+
loadAbsoluteMobileUpdateServerModule: () => loadAbsoluteMobileUpdateServerModule,
|
|
20660
|
+
renderAbsoluteMobileUpdateRegistry: () => renderAbsoluteMobileUpdateRegistry,
|
|
20661
|
+
writeAbsoluteMobileUpdateRegistry: () => writeAbsoluteMobileUpdateRegistry
|
|
20662
|
+
});
|
|
20663
|
+
import {
|
|
20664
|
+
createPrivateKey as createPrivateKey2,
|
|
20665
|
+
createPublicKey as createPublicKey3,
|
|
20666
|
+
X509Certificate as X509Certificate4
|
|
20667
|
+
} from "crypto";
|
|
20668
|
+
import { access as access16, mkdir as mkdir18 } from "fs/promises";
|
|
20669
|
+
import { dirname as dirname19, isAbsolute as isAbsolute9, relative as relative17, resolve as resolve20, sep as sep11 } from "path";
|
|
20670
|
+
import { pathToFileURL as pathToFileURL5 } from "url";
|
|
20671
|
+
import { Elysia as Elysia4 } from "elysia";
|
|
20672
|
+
var ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT = 1, DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE = "mobile.update.ts", object7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), projectPath3 = (projectRoot, requested) => {
|
|
20673
|
+
const root = resolve20(projectRoot);
|
|
20674
|
+
const path2 = resolve20(root, requested);
|
|
20675
|
+
const projectRelative = relative17(root, path2);
|
|
20676
|
+
if (projectRelative === ".." || projectRelative.startsWith(`..${sep11}`) || isAbsolute9(projectRelative))
|
|
20677
|
+
throw new TypeError("mobile.updates.server.registry must remain inside the project.");
|
|
20678
|
+
return path2;
|
|
20679
|
+
}, isRegistry4 = (value) => object7(value) && [
|
|
20680
|
+
"publishUpdate",
|
|
20681
|
+
"promoteUpdate",
|
|
20682
|
+
"rollbackUpdate",
|
|
20683
|
+
"resolveUpdate",
|
|
20684
|
+
"readUpdateFile"
|
|
20685
|
+
].every((method) => typeof value[method] === "function"), serverMetadata = (value) => {
|
|
20686
|
+
if (!object7(value))
|
|
20687
|
+
throw new TypeError("Mobile update registry must export valid absoluteMobileUpdateServer metadata. Run `absolute mobile update provision`.");
|
|
20688
|
+
const { format, provider, storage } = value;
|
|
20689
|
+
if (format !== ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT || storage !== "local" && storage !== "durable" || typeof provider !== "string" || !provider)
|
|
20690
|
+
throw new TypeError("Mobile update registry must export valid absoluteMobileUpdateServer metadata. Run `absolute mobile update provision`.");
|
|
20691
|
+
const metadata = {
|
|
20692
|
+
format: ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT,
|
|
20693
|
+
provider,
|
|
20694
|
+
storage
|
|
20695
|
+
};
|
|
20696
|
+
return metadata;
|
|
20697
|
+
}, loadAbsoluteMobileUpdateServerModule = async (projectRoot, requestedModulePath = DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE) => {
|
|
20698
|
+
const modulePath = projectPath3(projectRoot, requestedModulePath);
|
|
20699
|
+
await access16(modulePath).catch(() => {
|
|
20700
|
+
throw new TypeError(`Mobile update server registry does not exist: ${modulePath}. Run \`absolute mobile update provision\`.`);
|
|
20701
|
+
});
|
|
20702
|
+
const loaded = await import(pathToFileURL5(modulePath).href);
|
|
20703
|
+
if (!object7(loaded))
|
|
20704
|
+
throw new TypeError("Mobile update registry module has no exports.");
|
|
20705
|
+
const registry4 = loaded.default ?? loaded.registry;
|
|
20706
|
+
if (!isRegistry4(registry4))
|
|
20707
|
+
throw new TypeError("Mobile update registry must implement publication, promotion, rollback, resolution, and file reads.");
|
|
20708
|
+
return {
|
|
20709
|
+
metadata: serverMetadata(loaded.absoluteMobileUpdateServer),
|
|
20710
|
+
registry: registry4
|
|
20711
|
+
};
|
|
20712
|
+
}, expoSigningOptions = (config) => {
|
|
20713
|
+
if (!config.updates?.expoCodeSigning)
|
|
20714
|
+
return;
|
|
20715
|
+
const entries = Object.entries(config.updateServer?.expoCodeSigningKeys ?? {});
|
|
20716
|
+
const keys = Object.fromEntries(entries.map(([keyId, key]) => {
|
|
20717
|
+
const privateKey = process.env[key.privateKeyEnv];
|
|
20718
|
+
if (!privateKey)
|
|
20719
|
+
throw new TypeError(`Expo update serving requires ${key.privateKeyEnv} on the trusted server.`);
|
|
20720
|
+
try {
|
|
20721
|
+
const certificate = new X509Certificate4(key.certificatePem);
|
|
20722
|
+
const expected = certificate.publicKey.export({
|
|
20723
|
+
format: "der",
|
|
20724
|
+
type: "spki"
|
|
20725
|
+
});
|
|
20726
|
+
const actual = createPublicKey3(createPrivateKey2(privateKey)).export({
|
|
20727
|
+
format: "der",
|
|
20728
|
+
type: "spki"
|
|
20729
|
+
});
|
|
20730
|
+
if (!expected.equals(actual))
|
|
20731
|
+
throw new Error("key mismatch");
|
|
20732
|
+
} catch (error) {
|
|
20733
|
+
throw new TypeError(`${key.privateKeyEnv} must contain the RSA private key matching Expo update key ${keyId}.`, { cause: error });
|
|
20734
|
+
}
|
|
20735
|
+
return [keyId, { certificate: key.certificatePem, privateKey }];
|
|
20736
|
+
}));
|
|
20737
|
+
return { keys };
|
|
20738
|
+
}, createAbsoluteMobileUpdateServerPlugin = async (config, projectRoot, options = {}) => {
|
|
20739
|
+
const { updates, updateServer: server } = config;
|
|
20740
|
+
if (!updates || !server?.autoMount)
|
|
20741
|
+
return new Elysia4({ name: "absolutejs-mobile-updates-disabled" });
|
|
20742
|
+
const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, server.registryModule);
|
|
20743
|
+
if (options.production && module.metadata.storage !== "durable")
|
|
20744
|
+
throw new TypeError("Mobile production updates require durable object storage. Re-run `absolute mobile update provision --storage s3 --force` or configure a durable adapter.");
|
|
20745
|
+
const manifest = new URL(updates.manifestUrl);
|
|
20746
|
+
if (!manifest.pathname.endsWith("/update.json"))
|
|
20747
|
+
throw new TypeError("Auto-mounted mobile update manifests must end in /update.json.");
|
|
20748
|
+
const route = manifest.pathname.slice(0, -"/update.json".length);
|
|
20749
|
+
const { createMobileUpdateHandler: createMobileUpdateHandler2 } = await Promise.resolve().then(() => (init_mobileUpdate(), exports_mobileUpdate));
|
|
20750
|
+
const handler = createMobileUpdateHandler2({
|
|
20751
|
+
appId: config.appId,
|
|
20752
|
+
channel: updates.channel,
|
|
20753
|
+
...config.engine === "expo" ? { expoCodeSigning: expoSigningOptions(config) } : {},
|
|
20754
|
+
registry: module.registry,
|
|
20755
|
+
route
|
|
20756
|
+
});
|
|
20757
|
+
return new Elysia4({ name: "absolutejs-mobile-updates" }).all(`${route}/*`, ({ request }) => handler(request));
|
|
20758
|
+
}, inspectAbsoluteMobileUpdateServer = async (config, projectRoot) => {
|
|
20759
|
+
if (!config.updates)
|
|
20760
|
+
return;
|
|
20761
|
+
const module = await loadAbsoluteMobileUpdateServerModule(projectRoot, config.updateServer?.registryModule);
|
|
20762
|
+
if (module.metadata.storage !== "durable")
|
|
20763
|
+
throw new TypeError("Mobile production updates require durable object storage; the configured registry is local-only.");
|
|
20764
|
+
if (config.engine === "expo")
|
|
20765
|
+
expoSigningOptions(config);
|
|
20766
|
+
return module.metadata;
|
|
20767
|
+
}, publicKeysSource = (publicKeys) => JSON.stringify(publicKeys, null, "\t"), renderAbsoluteMobileUpdateRegistry = (options) => {
|
|
20768
|
+
const metadata = `export const absoluteMobileUpdateServer = {
|
|
20769
|
+
format: 1,
|
|
20770
|
+
provider: '${options.storage}',
|
|
20771
|
+
storage: '${options.storage === "local" ? "local" : "durable"}'
|
|
20772
|
+
} as const;`;
|
|
20773
|
+
if (options.storage === "local")
|
|
20774
|
+
return `import { fileURLToPath } from 'node:url';
|
|
20775
|
+
import { localBlobStore } from '@absolutejs/blob/local';
|
|
20776
|
+
import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
|
|
20777
|
+
|
|
20778
|
+
${metadata}
|
|
20779
|
+
|
|
20780
|
+
const store = localBlobStore({
|
|
20781
|
+
root: process.env.ABSOLUTE_MOBILE_UPDATE_LOCAL_ROOT ??
|
|
20782
|
+
fileURLToPath(new URL('./.absolutejs/mobile/update-registry/', import.meta.url))
|
|
20783
|
+
});
|
|
20784
|
+
|
|
20785
|
+
export default createMobileUpdateRegistry({
|
|
20786
|
+
publicKeys: ${publicKeysSource(options.publicKeys)},
|
|
20787
|
+
store
|
|
20788
|
+
});
|
|
20789
|
+
`;
|
|
20790
|
+
return `import { S3Client } from '@aws-sdk/client-s3';
|
|
20791
|
+
import { awsS3BlobStore } from '@absolutejs/blob/aws-s3';
|
|
20792
|
+
import { createMobileUpdateRegistry } from '@absolutejs/deploy/mobile-update';
|
|
20793
|
+
|
|
20794
|
+
${metadata}
|
|
20795
|
+
|
|
20796
|
+
const required = (name: string) => {
|
|
20797
|
+
const value = process.env[name];
|
|
20798
|
+
if (!value) throw new Error(\`Missing \${name}\`);
|
|
20799
|
+
return value;
|
|
20800
|
+
};
|
|
20801
|
+
|
|
20802
|
+
const client = new S3Client({
|
|
20803
|
+
region: process.env.ABSOLUTE_MOBILE_UPDATE_S3_REGION ?? 'auto',
|
|
20804
|
+
forcePathStyle: process.env.ABSOLUTE_MOBILE_UPDATE_S3_FORCE_PATH_STYLE === '1',
|
|
20805
|
+
...(process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT
|
|
20806
|
+
? { endpoint: process.env.ABSOLUTE_MOBILE_UPDATE_S3_ENDPOINT }
|
|
20807
|
+
: {})
|
|
20808
|
+
});
|
|
20809
|
+
const store = awsS3BlobStore({
|
|
20810
|
+
bucket: required('ABSOLUTE_MOBILE_UPDATE_S3_BUCKET'),
|
|
20811
|
+
client
|
|
20812
|
+
});
|
|
20813
|
+
|
|
20814
|
+
export default createMobileUpdateRegistry({
|
|
20815
|
+
publicKeys: ${publicKeysSource(options.publicKeys)},
|
|
20816
|
+
store
|
|
20817
|
+
});
|
|
20818
|
+
`;
|
|
20819
|
+
}, writeAbsoluteMobileUpdateRegistry = async (options) => {
|
|
20820
|
+
const path2 = projectPath3(options.projectRoot, options.modulePath ?? DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE);
|
|
20821
|
+
if (!options.force) {
|
|
20822
|
+
await access16(path2).then(() => {
|
|
20823
|
+
throw new TypeError(`Mobile update registry already exists: ${path2}. Pass --force to replace it.`);
|
|
20824
|
+
}, () => {
|
|
20825
|
+
return;
|
|
20826
|
+
});
|
|
20827
|
+
}
|
|
20828
|
+
await mkdir18(dirname19(path2), { recursive: true });
|
|
20829
|
+
await Bun.write(path2, renderAbsoluteMobileUpdateRegistry({
|
|
20830
|
+
publicKeys: options.publicKeys,
|
|
20831
|
+
storage: options.storage
|
|
20832
|
+
}));
|
|
20833
|
+
return path2;
|
|
20834
|
+
};
|
|
20835
|
+
var init_updateServer = () => {};
|
|
20836
|
+
|
|
19905
20837
|
// src/mobile/index.ts
|
|
19906
20838
|
init_artifactStore();
|
|
19907
20839
|
|
|
@@ -37789,6 +38721,9 @@ var parseAbsoluteMobileUpdateRequest = (request) => {
|
|
|
37789
38721
|
throw new TypeError("Mobile update runtime identity is invalid.");
|
|
37790
38722
|
return identity;
|
|
37791
38723
|
};
|
|
38724
|
+
|
|
38725
|
+
// src/mobile/index.ts
|
|
38726
|
+
init_updateServer();
|
|
37792
38727
|
export {
|
|
37793
38728
|
ABSOLUTE_ANDROID_RELEASE_FORMAT,
|
|
37794
38729
|
ABSOLUTE_AUTH_PACKAGE,
|
|
@@ -37822,6 +38757,7 @@ export {
|
|
|
37822
38757
|
ABSOLUTE_MOBILE_UPDATE_MAX_FILE_BYTES,
|
|
37823
38758
|
ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES,
|
|
37824
38759
|
ABSOLUTE_MOBILE_UPDATE_RUNTIME_FORMAT,
|
|
38760
|
+
ABSOLUTE_MOBILE_UPDATE_SERVER_FORMAT,
|
|
37825
38761
|
ABSOLUTE_MOBILE_UPDATE_SIGNATURE_ALGORITHM,
|
|
37826
38762
|
ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
|
|
37827
38763
|
ABSOLUTE_NATIVE_AUTH_SCOPES,
|
|
@@ -37834,6 +38770,7 @@ export {
|
|
|
37834
38770
|
ANDROID_ASSOCIATION_PATH,
|
|
37835
38771
|
APPLE_ASSOCIATION_PATH,
|
|
37836
38772
|
AbsoluteMobilePageProtocolError,
|
|
38773
|
+
DEFAULT_MOBILE_UPDATE_REGISTRY_MODULE,
|
|
37837
38774
|
MOBILE_PAGE_REQUEST_HEADERS,
|
|
37838
38775
|
absoluteDeviceNativeRequirements,
|
|
37839
38776
|
absoluteExpoExecutable,
|
|
@@ -37886,6 +38823,7 @@ export {
|
|
|
37886
38823
|
createAbsoluteMobileRouteMetadataPlugin,
|
|
37887
38824
|
createAbsoluteMobileUpdateClient,
|
|
37888
38825
|
createAbsoluteMobileUpdateRuntimeDescriptor,
|
|
38826
|
+
createAbsoluteMobileUpdateServerPlugin,
|
|
37889
38827
|
createAbsoluteMobileUpgradeResponse,
|
|
37890
38828
|
createAbsoluteRemoteExpoIosDevProject,
|
|
37891
38829
|
createAbsoluteRemoteIosDevProject,
|
|
@@ -37910,6 +38848,7 @@ export {
|
|
|
37910
38848
|
hashAbsoluteMobilePropsSchema,
|
|
37911
38849
|
inspectAbsoluteAndroidInstalledApp,
|
|
37912
38850
|
inspectAbsoluteMobileRouteMetadata,
|
|
38851
|
+
inspectAbsoluteMobileUpdateServer,
|
|
37913
38852
|
inspectAbsoluteRemoteMac,
|
|
37914
38853
|
inspectAbsoluteRemoteMacLanHost,
|
|
37915
38854
|
inspectAbsoluteRemoteMacWorkspace,
|
|
@@ -37928,6 +38867,7 @@ export {
|
|
|
37928
38867
|
loadAbsoluteDeviceCapabilityProviders,
|
|
37929
38868
|
loadAbsoluteMobileMaterializedBundle,
|
|
37930
38869
|
loadAbsoluteMobileUpdatePublisher,
|
|
38870
|
+
loadAbsoluteMobileUpdateServerModule,
|
|
37931
38871
|
loadAbsoluteNativeReleasePublisher,
|
|
37932
38872
|
matchesAbsoluteMobileRoutePattern,
|
|
37933
38873
|
materializeAbsoluteCapacitorWebBundle,
|
|
@@ -37974,6 +38914,7 @@ export {
|
|
|
37974
38914
|
readAbsoluteMobileUpdate,
|
|
37975
38915
|
redactAbsoluteIosLog,
|
|
37976
38916
|
removeAbsoluteRemoteMacProfile,
|
|
38917
|
+
renderAbsoluteMobileUpdateRegistry,
|
|
37977
38918
|
repairAbsoluteIosDevSession,
|
|
37978
38919
|
requestAbsoluteMobileBack,
|
|
37979
38920
|
requireAbsoluteIosReleaseMetadata,
|
|
@@ -38009,8 +38950,9 @@ export {
|
|
|
38009
38950
|
waitForAbsoluteIosHmrLog,
|
|
38010
38951
|
writeAbsoluteCapacitorConfig,
|
|
38011
38952
|
writeAbsoluteExpoProject,
|
|
38012
|
-
writeAbsoluteMobileGithubWorkflow
|
|
38953
|
+
writeAbsoluteMobileGithubWorkflow,
|
|
38954
|
+
writeAbsoluteMobileUpdateRegistry
|
|
38013
38955
|
};
|
|
38014
38956
|
|
|
38015
|
-
//# debugId=
|
|
38957
|
+
//# debugId=E2398F592E2828EE64756E2164756E21
|
|
38016
38958
|
//# sourceMappingURL=index.js.map
|