@hot-updater/aws 1.0.0-rc.43 → 1.0.0-rc.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +16 -12
- package/dist/index.d.cts +7 -9
- package/dist/index.d.mts +7 -9
- package/dist/index.mjs +14 -10
- package/dist/init/index.cjs +11 -9
- package/dist/init/index.mjs +8 -6
- package/dist/lambda/index.cjs +63 -135
- package/package.json +8 -7
package/dist/index.cjs
CHANGED
|
@@ -7,9 +7,7 @@ let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
|
7
7
|
let _aws_sdk_cloudfront_signer = require("@aws-sdk/cloudfront-signer");
|
|
8
8
|
let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
9
9
|
let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
|
|
10
|
-
let
|
|
11
|
-
let _hot_updater_server_plugins_insights = require("@hot-updater/server/plugins/insights");
|
|
12
|
-
let _hot_updater_server_plugins_remote_config = require("@hot-updater/server/plugins/remote-config");
|
|
10
|
+
let _hot_updater_server_plugins = require("@hot-updater/server/plugins");
|
|
13
11
|
//#region src/cloudFrontInvalidation.ts
|
|
14
12
|
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
15
13
|
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
@@ -438,10 +436,11 @@ const cloudFrontDownloadUrl = (config) => async ({ storageUri }) => {
|
|
|
438
436
|
};
|
|
439
437
|
//#endregion
|
|
440
438
|
//#region src/s3Storage.ts
|
|
439
|
+
/** Devices download right after the update check that returns the URL. */
|
|
440
|
+
const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
|
|
441
441
|
const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
|
|
442
442
|
function s3Storage(config) {
|
|
443
|
-
const { bucketName, basePath,
|
|
444
|
-
const getDownloadUrl = configuredGetDownloadUrl ?? (downloadUrlSigningKey ? (0, _hot_updater_plugin_core.createStorageDownloadUrl)(downloadUrlSigningKey) : void 0);
|
|
443
|
+
const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
|
|
445
444
|
const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
446
445
|
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
447
446
|
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(normalizedBasePath);
|
|
@@ -554,10 +553,15 @@ function s3Storage(config) {
|
|
|
554
553
|
throw error;
|
|
555
554
|
}
|
|
556
555
|
},
|
|
557
|
-
|
|
558
|
-
parseAndValidate(
|
|
559
|
-
return getDownloadUrl(
|
|
560
|
-
|
|
556
|
+
async getDownloadUrl({ storageUri }) {
|
|
557
|
+
const { key } = parseAndValidate(storageUri);
|
|
558
|
+
if (getDownloadUrl) return getDownloadUrl({ storageUri });
|
|
559
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
560
|
+
return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
|
|
561
|
+
Bucket: bucketName,
|
|
562
|
+
Key: key
|
|
563
|
+
}), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
|
|
564
|
+
},
|
|
561
565
|
async exists({ storageUri }) {
|
|
562
566
|
const { key } = parseAndValidate(storageUri);
|
|
563
567
|
try {
|
|
@@ -588,9 +592,9 @@ function s3Storage(config) {
|
|
|
588
592
|
* client route, and Remote Config. Its Lambda@Edge function imports this list.
|
|
589
593
|
*/
|
|
590
594
|
const plugins = [
|
|
591
|
-
(0,
|
|
592
|
-
(0,
|
|
593
|
-
(0,
|
|
595
|
+
(0, _hot_updater_server_plugins.insights)(),
|
|
596
|
+
(0, _hot_updater_server_plugins.apiKeys)(),
|
|
597
|
+
(0, _hot_updater_server_plugins.remoteConfig)()
|
|
594
598
|
];
|
|
595
599
|
//#endregion
|
|
596
600
|
exports.cloudFrontDownloadUrl = cloudFrontDownloadUrl;
|
package/dist/index.d.cts
CHANGED
|
@@ -56,16 +56,14 @@ interface S3StorageConfig extends S3ClientConfig {
|
|
|
56
56
|
bucketName: string;
|
|
57
57
|
/** Base path where bundles will be stored in the bucket. */
|
|
58
58
|
basePath?: string;
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The URL devices download an object from, such as
|
|
61
|
+
* `cloudFrontDownloadUrl(...)`. Without it, a presigned S3 URL that
|
|
62
|
+
* expires in an hour.
|
|
63
|
+
*/
|
|
60
64
|
getDownloadUrl?: StorageAdapter["getDownloadUrl"];
|
|
61
65
|
}
|
|
62
|
-
type
|
|
63
|
-
downloadUrlSigningKey: string;
|
|
64
|
-
} | {
|
|
65
|
-
getDownloadUrl: NonNullable<StorageAdapter["getDownloadUrl"]>;
|
|
66
|
-
});
|
|
67
|
-
type S3StorageOperations = "put" | "get" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
68
|
-
declare function s3Storage(config: S3StorageConfigWithDownloadUrl): StorageAdapterWith<S3StorageOperations | "getDownloadUrl">;
|
|
66
|
+
type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
69
67
|
declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
|
|
70
68
|
//#endregion
|
|
71
69
|
//#region ../insights/dist/server/eventRow.d.mts
|
|
@@ -1081,4 +1079,4 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
1081
1079
|
endpoints: readonly import("@hot-updater/plugin-core").PluginEndpoint[];
|
|
1082
1080
|
}, {}>];
|
|
1083
1081
|
//#endregion
|
|
1084
|
-
export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig,
|
|
1082
|
+
export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig, cloudFrontDownloadUrl, dynamoDB, migrateDynamoDB, plugins, s3Storage };
|
package/dist/index.d.mts
CHANGED
|
@@ -56,16 +56,14 @@ interface S3StorageConfig extends S3ClientConfig {
|
|
|
56
56
|
bucketName: string;
|
|
57
57
|
/** Base path where bundles will be stored in the bucket. */
|
|
58
58
|
basePath?: string;
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The URL devices download an object from, such as
|
|
61
|
+
* `cloudFrontDownloadUrl(...)`. Without it, a presigned S3 URL that
|
|
62
|
+
* expires in an hour.
|
|
63
|
+
*/
|
|
60
64
|
getDownloadUrl?: StorageAdapter["getDownloadUrl"];
|
|
61
65
|
}
|
|
62
|
-
type
|
|
63
|
-
downloadUrlSigningKey: string;
|
|
64
|
-
} | {
|
|
65
|
-
getDownloadUrl: NonNullable<StorageAdapter["getDownloadUrl"]>;
|
|
66
|
-
});
|
|
67
|
-
type S3StorageOperations = "put" | "get" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
68
|
-
declare function s3Storage(config: S3StorageConfigWithDownloadUrl): StorageAdapterWith<S3StorageOperations | "getDownloadUrl">;
|
|
66
|
+
type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
69
67
|
declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
|
|
70
68
|
//#endregion
|
|
71
69
|
//#region ../insights/dist/server/eventRow.d.mts
|
|
@@ -1081,4 +1079,4 @@ declare const plugins: readonly [import("@hot-updater/plugin-core").HotUpdaterPl
|
|
|
1081
1079
|
endpoints: readonly import("@hot-updater/plugin-core").PluginEndpoint[];
|
|
1082
1080
|
}, {}>];
|
|
1083
1081
|
//#endregion
|
|
1084
|
-
export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig,
|
|
1082
|
+
export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig, cloudFrontDownloadUrl, dynamoDB, migrateDynamoDB, plugins, s3Storage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { CreateTableCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, UpdateTimeToLiveCommand, waitUntilTableExists } from "@aws-sdk/client-dynamodb";
|
|
2
|
-
import { createEngineDatabase, createKvAdapter, createStorageAdapter,
|
|
2
|
+
import { createEngineDatabase, createKvAdapter, createStorageAdapter, createStorageKeyBuilder, createStorageUri, migrateCoreSchema, parseStorageUri } from "@hot-updater/plugin-core";
|
|
3
3
|
import { CloudFrontClient, CreateInvalidationCommand, GetInvalidationCommand } from "@aws-sdk/client-cloudfront";
|
|
4
4
|
import { BatchGetCommand, BatchWriteCommand, DynamoDBDocumentClient, QueryCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
|
|
5
5
|
import { SSM } from "@aws-sdk/client-ssm";
|
|
6
6
|
import { getSignedUrl } from "@aws-sdk/cloudfront-signer";
|
|
7
7
|
import { DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, S3Client } from "@aws-sdk/client-s3";
|
|
8
8
|
import { Upload } from "@aws-sdk/lib-storage";
|
|
9
|
-
import { apiKeys } from "@hot-updater/server/plugins
|
|
10
|
-
import { insights } from "@hot-updater/server/plugins/insights";
|
|
11
|
-
import { remoteConfig } from "@hot-updater/server/plugins/remote-config";
|
|
9
|
+
import { apiKeys, insights, remoteConfig } from "@hot-updater/server/plugins";
|
|
12
10
|
//#region src/cloudFrontInvalidation.ts
|
|
13
11
|
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
14
12
|
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
@@ -437,10 +435,11 @@ const cloudFrontDownloadUrl = (config) => async ({ storageUri }) => {
|
|
|
437
435
|
};
|
|
438
436
|
//#endregion
|
|
439
437
|
//#region src/s3Storage.ts
|
|
438
|
+
/** Devices download right after the update check that returns the URL. */
|
|
439
|
+
const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
|
|
440
440
|
const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
|
|
441
441
|
function s3Storage(config) {
|
|
442
|
-
const { bucketName, basePath,
|
|
443
|
-
const getDownloadUrl = configuredGetDownloadUrl ?? (downloadUrlSigningKey ? createStorageDownloadUrl(downloadUrlSigningKey) : void 0);
|
|
442
|
+
const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
|
|
444
443
|
const client = new S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
445
444
|
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
446
445
|
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
@@ -553,10 +552,15 @@ function s3Storage(config) {
|
|
|
553
552
|
throw error;
|
|
554
553
|
}
|
|
555
554
|
},
|
|
556
|
-
|
|
557
|
-
parseAndValidate(
|
|
558
|
-
return getDownloadUrl(
|
|
559
|
-
|
|
555
|
+
async getDownloadUrl({ storageUri }) {
|
|
556
|
+
const { key } = parseAndValidate(storageUri);
|
|
557
|
+
if (getDownloadUrl) return getDownloadUrl({ storageUri });
|
|
558
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
559
|
+
return { url: await getSignedUrl(client, new GetObjectCommand({
|
|
560
|
+
Bucket: bucketName,
|
|
561
|
+
Key: key
|
|
562
|
+
}), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
|
|
563
|
+
},
|
|
560
564
|
async exists({ storageUri }) {
|
|
561
565
|
const { key } = parseAndValidate(storageUri);
|
|
562
566
|
try {
|
package/dist/init/index.cjs
CHANGED
|
@@ -47,9 +47,7 @@ let _aws_sdk_client_dynamodb = require("@aws-sdk/client-dynamodb");
|
|
|
47
47
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
48
48
|
let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
|
|
49
49
|
let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
50
|
-
let
|
|
51
|
-
let _hot_updater_server_plugins_insights = require("@hot-updater/server/plugins/insights");
|
|
52
|
-
let _hot_updater_server_plugins_remote_config = require("@hot-updater/server/plugins/remote-config");
|
|
50
|
+
let _hot_updater_server_plugins = require("@hot-updater/server/plugins");
|
|
53
51
|
let _aws_sdk_credential_providers = require("@aws-sdk/credential-providers");
|
|
54
52
|
let _aws_sdk_client_lambda = require("@aws-sdk/client-lambda");
|
|
55
53
|
let crypto$1 = require("crypto");
|
|
@@ -6633,9 +6631,9 @@ const dynamoDB = (config) => {
|
|
|
6633
6631
|
* client route, and Remote Config. Its Lambda@Edge function imports this list.
|
|
6634
6632
|
*/
|
|
6635
6633
|
const plugins = [
|
|
6636
|
-
(0,
|
|
6637
|
-
(0,
|
|
6638
|
-
(0,
|
|
6634
|
+
(0, _hot_updater_server_plugins.insights)(),
|
|
6635
|
+
(0, _hot_updater_server_plugins.apiKeys)(),
|
|
6636
|
+
(0, _hot_updater_server_plugins.remoteConfig)()
|
|
6639
6637
|
];
|
|
6640
6638
|
//#endregion
|
|
6641
6639
|
//#region iac/init/index.ts
|
|
@@ -8457,10 +8455,14 @@ const awsOptions = {
|
|
|
8457
8455
|
}
|
|
8458
8456
|
const builder = new _hot_updater_cli_tools.ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setPlugins({
|
|
8459
8457
|
imports: [{
|
|
8460
|
-
pkg: "
|
|
8461
|
-
named: [
|
|
8458
|
+
pkg: "hot-updater/plugins",
|
|
8459
|
+
named: [
|
|
8460
|
+
"apiKeys",
|
|
8461
|
+
"insights",
|
|
8462
|
+
"remoteConfig"
|
|
8463
|
+
]
|
|
8462
8464
|
}],
|
|
8463
|
-
configString: "
|
|
8465
|
+
configString: "[apiKeys(), insights(), remoteConfig()]"
|
|
8464
8466
|
}).setIntermediateCode(helperStatements.map((statement) => statement.code.trim()).join("\n\n"));
|
|
8465
8467
|
switch (authMode.mode) {
|
|
8466
8468
|
case "sso":
|
package/dist/init/index.mjs
CHANGED
|
@@ -20,9 +20,7 @@ import { CreateTableCommand, DescribeTableCommand, DescribeTimeToLiveCommand, Dy
|
|
|
20
20
|
import { SETTINGS_TABLE, aggregateBatchingTables, createEngineDatabase, createKvAdapter, encodeKvKey, migrateCoreSchema, toolingTargetOf } from "@hot-updater/plugin-core";
|
|
21
21
|
import { CloudFront, CloudFrontClient, CreateInvalidationCommand, GetInvalidationCommand } from "@aws-sdk/client-cloudfront";
|
|
22
22
|
import { BatchGetCommand, BatchWriteCommand, DynamoDBDocumentClient, QueryCommand, TransactWriteCommand } from "@aws-sdk/lib-dynamodb";
|
|
23
|
-
import { apiKeys } from "@hot-updater/server/plugins
|
|
24
|
-
import { insights } from "@hot-updater/server/plugins/insights";
|
|
25
|
-
import { remoteConfig } from "@hot-updater/server/plugins/remote-config";
|
|
23
|
+
import { apiKeys, insights, remoteConfig } from "@hot-updater/server/plugins";
|
|
26
24
|
import { fromIni, fromNodeProviderChain, fromSSO } from "@aws-sdk/credential-providers";
|
|
27
25
|
import { Lambda } from "@aws-sdk/client-lambda";
|
|
28
26
|
import crypto$1 from "crypto";
|
|
@@ -8452,10 +8450,14 @@ const awsOptions = {
|
|
|
8452
8450
|
}
|
|
8453
8451
|
const builder = new ConfigBuilder().setBuildType(build).setStorage(storageConfig).setDatabase(databaseConfig).setPlugins({
|
|
8454
8452
|
imports: [{
|
|
8455
|
-
pkg: "
|
|
8456
|
-
named: [
|
|
8453
|
+
pkg: "hot-updater/plugins",
|
|
8454
|
+
named: [
|
|
8455
|
+
"apiKeys",
|
|
8456
|
+
"insights",
|
|
8457
|
+
"remoteConfig"
|
|
8458
|
+
]
|
|
8457
8459
|
}],
|
|
8458
|
-
configString: "
|
|
8460
|
+
configString: "[apiKeys(), insights(), remoteConfig()]"
|
|
8459
8461
|
}).setIntermediateCode(helperStatements.map((statement) => statement.code.trim()).join("\n\n"));
|
|
8460
8462
|
switch (authMode.mode) {
|
|
8461
8463
|
case "sso":
|
package/dist/lambda/index.cjs
CHANGED
|
@@ -1451,52 +1451,6 @@ var ReleaseCatalogMutationError = class extends Error {
|
|
|
1451
1451
|
}
|
|
1452
1452
|
};
|
|
1453
1453
|
//#endregion
|
|
1454
|
-
//#region ../plugin-core/dist/storageDownloadPath.mjs
|
|
1455
|
-
const encodeBase64Url$1 = (value) => {
|
|
1456
|
-
let binary = "";
|
|
1457
|
-
for (const byte of new TextEncoder().encode(value)) binary += String.fromCharCode(byte);
|
|
1458
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
1459
|
-
};
|
|
1460
|
-
const decodeBase64Url$1 = (value) => {
|
|
1461
|
-
try {
|
|
1462
|
-
const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
|
|
1463
|
-
const binary = atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="));
|
|
1464
|
-
return new TextDecoder(void 0, { fatal: true }).decode(Uint8Array.from(binary, (character) => character.charCodeAt(0)));
|
|
1465
|
-
} catch {
|
|
1466
|
-
return null;
|
|
1467
|
-
}
|
|
1468
|
-
};
|
|
1469
|
-
const encodeBytes = (bytes) => {
|
|
1470
|
-
let binary = "";
|
|
1471
|
-
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
1472
|
-
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
1473
|
-
};
|
|
1474
|
-
const createStorageDownloadPath = (storageUri, signature) => `/storage/${encodeBase64Url$1(storageUri)}/${encodeURIComponent(signature)}`;
|
|
1475
|
-
const parseStorageDownloadPath = (path) => {
|
|
1476
|
-
const match = /^\/storage\/([^/]+)\/([^/]+)$/.exec(path);
|
|
1477
|
-
if (!match) return null;
|
|
1478
|
-
const storageUri = decodeBase64Url$1(match[1]);
|
|
1479
|
-
if (storageUri === null) return null;
|
|
1480
|
-
try {
|
|
1481
|
-
return {
|
|
1482
|
-
signature: decodeURIComponent(match[2]),
|
|
1483
|
-
storageUri
|
|
1484
|
-
};
|
|
1485
|
-
} catch {
|
|
1486
|
-
return null;
|
|
1487
|
-
}
|
|
1488
|
-
};
|
|
1489
|
-
const createStorageDownloadUrl = (signingKey) => {
|
|
1490
|
-
const key = crypto.subtle.importKey("raw", new TextEncoder().encode(signingKey), {
|
|
1491
|
-
hash: "SHA-256",
|
|
1492
|
-
name: "HMAC"
|
|
1493
|
-
}, false, ["sign"]);
|
|
1494
|
-
return async ({ storageUri }) => {
|
|
1495
|
-
const signature = encodeBytes(new Uint8Array(await crypto.subtle.sign("HMAC", await key, new TextEncoder().encode(storageUri))));
|
|
1496
|
-
return { url: createStorageDownloadPath(storageUri, signature) };
|
|
1497
|
-
};
|
|
1498
|
-
};
|
|
1499
|
-
//#endregion
|
|
1500
1454
|
//#region ../plugin-core/dist/serverPlugin/definePlugin.mjs
|
|
1501
1455
|
/** Declares a plugin; Hot Updater's own plugins and third-party ones use the same contract. */
|
|
1502
1456
|
const definePlugin = (plugin) => plugin;
|
|
@@ -4637,7 +4591,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
|
|
|
4637
4591
|
};
|
|
4638
4592
|
//#endregion
|
|
4639
4593
|
//#region ../../packages/server/dist/version.mjs
|
|
4640
|
-
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.
|
|
4594
|
+
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.44";
|
|
4641
4595
|
//#endregion
|
|
4642
4596
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
4643
4597
|
/**
|
|
@@ -6823,6 +6777,22 @@ const privateNotFound = () => Response.json({ error: "Not found" }, {
|
|
|
6823
6777
|
status: 404,
|
|
6824
6778
|
headers: { "cache-control": "private, no-store" }
|
|
6825
6779
|
});
|
|
6780
|
+
const badRequest = (error) => Response.json({ error }, {
|
|
6781
|
+
status: 400,
|
|
6782
|
+
headers: { "cache-control": "private, no-store" }
|
|
6783
|
+
});
|
|
6784
|
+
/**
|
|
6785
|
+
* A 400 for a catalog path whose scope is malformed, such as a channel name
|
|
6786
|
+
* where its base64url key belongs; null for a well-formed scope.
|
|
6787
|
+
*/
|
|
6788
|
+
const invalidScope = (input) => {
|
|
6789
|
+
try {
|
|
6790
|
+
createReleaseCatalogScopeKey(input);
|
|
6791
|
+
return null;
|
|
6792
|
+
} catch (error) {
|
|
6793
|
+
return badRequest(error instanceof Error ? error.message : "Invalid Release Catalog scope");
|
|
6794
|
+
}
|
|
6795
|
+
};
|
|
6826
6796
|
/**
|
|
6827
6797
|
* A scope with no catalog yet, such as a store version before its first
|
|
6828
6798
|
* OTA release: cached like a catalog, so its update checks don't all reach
|
|
@@ -6901,16 +6871,15 @@ const createReleaseCatalogRouteHandlers = () => {
|
|
|
6901
6871
|
appVersionReleaseCatalog: async (params, request, { core }) => {
|
|
6902
6872
|
const rawAppVersion = requireRouteParam(params, "appVersion");
|
|
6903
6873
|
const appVersion = canonicalizeAppVersion(rawAppVersion);
|
|
6904
|
-
if (appVersion === null || appVersion !== rawAppVersion) return
|
|
6905
|
-
status: 400,
|
|
6906
|
-
headers: { "cache-control": "private, no-store" }
|
|
6907
|
-
});
|
|
6874
|
+
if (appVersion === null || appVersion !== rawAppVersion) return badRequest("Invalid app version");
|
|
6908
6875
|
const input = {
|
|
6909
6876
|
appVersion,
|
|
6910
6877
|
channelKey: requireRouteParam(params, "channelKey"),
|
|
6911
6878
|
platform: requirePlatformParam(params),
|
|
6912
6879
|
strategy: "APP_VERSION"
|
|
6913
6880
|
};
|
|
6881
|
+
const invalid = invalidScope(input);
|
|
6882
|
+
if (invalid) return invalid;
|
|
6914
6883
|
return catalogResponse(await loadCatalog(`app-version:${JSON.stringify(input)}`, () => core.getReleaseCatalog(input)), request);
|
|
6915
6884
|
},
|
|
6916
6885
|
fingerprintReleaseCatalog: async (params, request, { core }) => {
|
|
@@ -6920,6 +6889,8 @@ const createReleaseCatalogRouteHandlers = () => {
|
|
|
6920
6889
|
platform: requirePlatformParam(params),
|
|
6921
6890
|
strategy: "FINGERPRINT"
|
|
6922
6891
|
};
|
|
6892
|
+
const invalid = invalidScope(input);
|
|
6893
|
+
if (invalid) return invalid;
|
|
6923
6894
|
return catalogResponse(await loadCatalog(`fingerprint:${JSON.stringify(input)}`, () => core.getReleaseCatalog(input)), request);
|
|
6924
6895
|
},
|
|
6925
6896
|
artifactV1: async (params, _request, { core }) => {
|
|
@@ -7001,8 +6972,8 @@ const errorResponse = (error, status) => Response.json({ error }, {
|
|
|
7001
6972
|
headers: { "cache-control": "private, no-store" },
|
|
7002
6973
|
status
|
|
7003
6974
|
});
|
|
7004
|
-
/** `/version`
|
|
7005
|
-
const PUBLIC_CLIENT_ROUTES = /* @__PURE__ */ new Set(["version"
|
|
6975
|
+
/** `/version` never passes through the client policy. */
|
|
6976
|
+
const PUBLIC_CLIENT_ROUTES = /* @__PURE__ */ new Set(["version"]);
|
|
7006
6977
|
const routesOf = Symbol.for("@hot-updater/server/routes");
|
|
7007
6978
|
const withVary = (response, varyHeaders) => {
|
|
7008
6979
|
const cacheControl = response.headers.get("cache-control") ?? "";
|
|
@@ -7047,26 +7018,11 @@ const createRequestHandler = ({ api, clientPolicy, privateResponses = false, rou
|
|
|
7047
7018
|
});
|
|
7048
7019
|
}
|
|
7049
7020
|
};
|
|
7050
|
-
|
|
7051
|
-
const token = params.token;
|
|
7052
|
-
const signature = params.signature;
|
|
7053
|
-
if (!token || !signature) return errorResponse("Not found", 404);
|
|
7054
|
-
const response = await downloadStorageObject(token, signature);
|
|
7055
|
-
if (!response) return errorResponse("Not found", 404);
|
|
7056
|
-
const headers = new Headers(response.headers);
|
|
7057
|
-
if (!headers.has("cache-control")) headers.set("cache-control", "public, max-age=31536000, immutable");
|
|
7058
|
-
return new Response(response.body, {
|
|
7059
|
-
headers,
|
|
7060
|
-
status: response.status,
|
|
7061
|
-
statusText: response.statusText
|
|
7062
|
-
});
|
|
7063
|
-
};
|
|
7064
|
-
function createHotUpdaterHandlers({ api, clientPolicy, downloadStorageObject, endpoints = [], plugins = [] }) {
|
|
7021
|
+
function createHotUpdaterHandlers({ api, clientPolicy, endpoints = [], plugins = [] }) {
|
|
7065
7022
|
const routeHandlers = {
|
|
7066
7023
|
...createVersionRouteHandlers(plugins),
|
|
7067
7024
|
...createReleaseCatalogRouteHandlers(),
|
|
7068
|
-
...createAdminRouteHandlers()
|
|
7069
|
-
...downloadStorageObject === void 0 ? {} : { downloadStorageObject: createDownloadStorageRouteHandler(downloadStorageObject) }
|
|
7025
|
+
...createAdminRouteHandlers()
|
|
7070
7026
|
};
|
|
7071
7027
|
const routes = [];
|
|
7072
7028
|
const shapes = /* @__PURE__ */ new Map();
|
|
@@ -7085,7 +7041,6 @@ function createHotUpdaterHandlers({ api, clientPolicy, downloadStorageObject, en
|
|
|
7085
7041
|
const clientRouter = createRouter();
|
|
7086
7042
|
const addClientRoute = mount(clientRouter, false);
|
|
7087
7043
|
addClientRoute("GET", "/version", "version");
|
|
7088
|
-
if (downloadStorageObject !== void 0) addClientRoute("GET", "/storage/:token/:signature", "downloadStorageObject");
|
|
7089
7044
|
addClientRoute("GET", "/release-catalogs/app-version/:platform/:channelKey/:appVersion", "appVersionReleaseCatalog");
|
|
7090
7045
|
addClientRoute("GET", "/release-catalogs/fingerprint/:platform/:channelKey/:fingerprintHash", "fingerprintReleaseCatalog");
|
|
7091
7046
|
addClientRoute("GET", "/artifacts/v1/:targetBundleId/from/:currentBundleId", "artifactV1");
|
|
@@ -7117,33 +7072,23 @@ function createHotUpdaterHandlers({ api, clientPolicy, downloadStorageObject, en
|
|
|
7117
7072
|
}
|
|
7118
7073
|
//#endregion
|
|
7119
7074
|
//#region ../../packages/server/dist/storageAccess.mjs
|
|
7075
|
+
/** A URL devices download from, which only an absolute HTTP(S) URL can be. */
|
|
7120
7076
|
const assertRemoteUrl = (value) => {
|
|
7121
|
-
|
|
7122
|
-
|
|
7077
|
+
let protocol;
|
|
7078
|
+
try {
|
|
7079
|
+
protocol = new URL(value).protocol;
|
|
7080
|
+
} catch {}
|
|
7081
|
+
if (protocol !== "http:" && protocol !== "https:") throw new Error("Storage getDownloadUrl must resolve to an absolute HTTP(S) URL.");
|
|
7123
7082
|
return value;
|
|
7124
7083
|
};
|
|
7125
7084
|
const getStorageProtocol = (storageUri) => new URL(storageUri).protocol.replace(":", "");
|
|
7126
7085
|
const isRemoteUrlProtocol = (protocol) => protocol === "http" || protocol === "https";
|
|
7127
|
-
|
|
7128
|
-
|
|
7129
|
-
|
|
7130
|
-
|
|
7131
|
-
|
|
7132
|
-
const
|
|
7133
|
-
const leftBytes = new TextEncoder().encode(left);
|
|
7134
|
-
const rightBytes = new TextEncoder().encode(right);
|
|
7135
|
-
let difference = leftBytes.length ^ rightBytes.length;
|
|
7136
|
-
const length = Math.max(leftBytes.length, rightBytes.length);
|
|
7137
|
-
for (let index = 0; index < length; index += 1) difference |= (leftBytes[index] ?? 0) ^ (rightBytes[index] ?? 0);
|
|
7138
|
-
return difference === 0;
|
|
7139
|
-
};
|
|
7140
|
-
const createStorageAccess = (storageAdapters) => {
|
|
7141
|
-
const protocols = /* @__PURE__ */ new Set();
|
|
7142
|
-
for (const storage of storageAdapters) {
|
|
7143
|
-
if (protocols.has(storage.protocol)) throw new Error(`Multiple storage adapters handle protocol: ${storage.protocol}`);
|
|
7144
|
-
protocols.add(storage.protocol);
|
|
7145
|
-
}
|
|
7146
|
-
const findStorage = (protocol) => storageAdapters.find((item) => item.protocol === protocol);
|
|
7086
|
+
/**
|
|
7087
|
+
* Reads and download URLs over the server's storage, for the URIs of its
|
|
7088
|
+
* protocol; an HTTP(S) URI is used as it is.
|
|
7089
|
+
*/
|
|
7090
|
+
const createStorageAccess = (storageAdapter) => {
|
|
7091
|
+
const findStorage = (protocol) => storageAdapter.protocol === protocol ? storageAdapter : void 0;
|
|
7147
7092
|
const readStorageResponse = async (storageUri) => {
|
|
7148
7093
|
const protocol = getStorageProtocol(storageUri);
|
|
7149
7094
|
const storage = findStorage(protocol);
|
|
@@ -7167,38 +7112,12 @@ const createStorageAccess = (storageAdapters) => {
|
|
|
7167
7112
|
}
|
|
7168
7113
|
if (!storage.getDownloadUrl) return null;
|
|
7169
7114
|
const { url: downloadUrl } = await storage.getDownloadUrl({ storageUri });
|
|
7170
|
-
|
|
7171
|
-
return assertRemoteUrl(downloadUrl);
|
|
7172
|
-
} catch (error) {
|
|
7173
|
-
if (/^[a-z][a-z\d+.-]*:/i.test(downloadUrl)) throw error;
|
|
7174
|
-
}
|
|
7175
|
-
return resolveDownloadPath(downloadUrl, storageUri);
|
|
7115
|
+
return assertRemoteUrl(downloadUrl);
|
|
7176
7116
|
};
|
|
7177
7117
|
const readStorageText = async (storageUri) => {
|
|
7178
7118
|
return (await readStorageResponse(storageUri))?.text() ?? null;
|
|
7179
7119
|
};
|
|
7180
7120
|
return {
|
|
7181
|
-
downloadStorageObject: storageAdapters.some((storage) => storage.getDownloadUrl !== void 0) ? async (storageUriToken, encodedSignature) => {
|
|
7182
|
-
const requestedPath = `/storage/${storageUriToken}/${encodedSignature}`;
|
|
7183
|
-
const requested = parseStorageDownloadPath(requestedPath);
|
|
7184
|
-
if (!requested) return null;
|
|
7185
|
-
let storage;
|
|
7186
|
-
try {
|
|
7187
|
-
storage = findStorage(getStorageProtocol(requested.storageUri));
|
|
7188
|
-
} catch {
|
|
7189
|
-
return null;
|
|
7190
|
-
}
|
|
7191
|
-
if (!storage?.getDownloadUrl || !storage.get) return null;
|
|
7192
|
-
const { url: downloadUrl } = await storage.getDownloadUrl({ storageUri: requested.storageUri });
|
|
7193
|
-
try {
|
|
7194
|
-
new URL(downloadUrl);
|
|
7195
|
-
return null;
|
|
7196
|
-
} catch {
|
|
7197
|
-
if (/^[a-z][a-z\d+.-]*:/i.test(downloadUrl)) return null;
|
|
7198
|
-
}
|
|
7199
|
-
if (!tokensEqual(downloadUrl, requestedPath)) return null;
|
|
7200
|
-
return (await storage.get({ storageUri: requested.storageUri })).response;
|
|
7201
|
-
} : void 0,
|
|
7202
7121
|
readStorageText,
|
|
7203
7122
|
resolveFileUrl
|
|
7204
7123
|
};
|
|
@@ -7220,17 +7139,21 @@ const databaseOf = (value) => {
|
|
|
7220
7139
|
}
|
|
7221
7140
|
throw new HotUpdaterConfigError("database must be a Hot Updater 1.0 database, such as kyselyAdapter(...) or postgres(...). Upgrade the provider package that created it.");
|
|
7222
7141
|
};
|
|
7223
|
-
/**
|
|
7142
|
+
/** The configured storage, when it is a storage adapter. */
|
|
7143
|
+
const storageOf = (value) => {
|
|
7144
|
+
if (isRecord$4(value) && typeof value.name === "string" && typeof value.protocol === "string") return value;
|
|
7145
|
+
throw new HotUpdaterConfigError("storage must be a storage adapter, such as s3Storage(...): the one hot-updater.config.ts uploads with.");
|
|
7146
|
+
};
|
|
7147
|
+
/** `"public"`, or nothing. */
|
|
7224
7148
|
const isPublic = (value) => {
|
|
7225
7149
|
if (value === void 0) return false;
|
|
7226
7150
|
if (value === "public") return true;
|
|
7227
|
-
throw new HotUpdaterConfigError(
|
|
7151
|
+
throw new HotUpdaterConfigError("clientAccess must be \"public\"; to protect client routes, add a plugin that provides clientAuth to plugins.");
|
|
7228
7152
|
};
|
|
7229
7153
|
function createHotUpdater(options) {
|
|
7230
|
-
for (const key of ["authorityId", "catalogId"]) if (Object.hasOwn(options, key)) throw new TypeError(`Remove ${key} from createHotUpdater options. Catalog identity is managed internally.`);
|
|
7231
7154
|
const database = databaseOf(options.database);
|
|
7232
|
-
const storage =
|
|
7233
|
-
const {
|
|
7155
|
+
const storage = storageOf(options.storage);
|
|
7156
|
+
const { readStorageText, resolveFileUrl } = createStorageAccess(storage);
|
|
7234
7157
|
const publicClients = isPublic(options.clientAccess);
|
|
7235
7158
|
const configured = Object.freeze([...options.plugins ?? []]);
|
|
7236
7159
|
const plugins = assemblePlugins(configured, database, { storage: {
|
|
@@ -7249,7 +7172,6 @@ function createHotUpdater(options) {
|
|
|
7249
7172
|
const handlers = createHotUpdaterHandlers({
|
|
7250
7173
|
api: { core: plugins.core },
|
|
7251
7174
|
...clientPolicy === void 0 ? {} : { clientPolicy },
|
|
7252
|
-
downloadStorageObject,
|
|
7253
7175
|
endpoints: plugins.endpoints,
|
|
7254
7176
|
plugins: Object.keys(plugins.api)
|
|
7255
7177
|
});
|
|
@@ -7258,7 +7180,7 @@ function createHotUpdater(options) {
|
|
|
7258
7180
|
adapterName: database.name,
|
|
7259
7181
|
get handlers() {
|
|
7260
7182
|
if (!serving) {
|
|
7261
|
-
|
|
7183
|
+
assertStorageOperations(storage, ["get", "getDownloadUrl"]);
|
|
7262
7184
|
serving = true;
|
|
7263
7185
|
}
|
|
7264
7186
|
return handlers;
|
|
@@ -13761,10 +13683,11 @@ const plugins = [
|
|
|
13761
13683
|
];
|
|
13762
13684
|
//#endregion
|
|
13763
13685
|
//#region src/s3Storage.ts
|
|
13686
|
+
/** Devices download right after the update check that returns the URL. */
|
|
13687
|
+
const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
|
|
13764
13688
|
const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
|
|
13765
13689
|
function s3Storage(config) {
|
|
13766
|
-
const { bucketName, basePath,
|
|
13767
|
-
const getDownloadUrl = configuredGetDownloadUrl ?? (downloadUrlSigningKey ? createStorageDownloadUrl(downloadUrlSigningKey) : void 0);
|
|
13690
|
+
const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
|
|
13768
13691
|
const client = new _aws_sdk_client_s3.S3Client(applyS3RuntimeAwsConfig(s3Config));
|
|
13769
13692
|
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
13770
13693
|
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
@@ -13877,10 +13800,15 @@ function s3Storage(config) {
|
|
|
13877
13800
|
throw error;
|
|
13878
13801
|
}
|
|
13879
13802
|
},
|
|
13880
|
-
|
|
13881
|
-
parseAndValidate(
|
|
13882
|
-
return getDownloadUrl(
|
|
13883
|
-
|
|
13803
|
+
async getDownloadUrl({ storageUri }) {
|
|
13804
|
+
const { key } = parseAndValidate(storageUri);
|
|
13805
|
+
if (getDownloadUrl) return getDownloadUrl({ storageUri });
|
|
13806
|
+
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
13807
|
+
return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
|
|
13808
|
+
Bucket: bucketName,
|
|
13809
|
+
Key: key
|
|
13810
|
+
}), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
|
|
13811
|
+
},
|
|
13884
13812
|
async exists({ storageUri }) {
|
|
13885
13813
|
const { key } = parseAndValidate(storageUri);
|
|
13886
13814
|
try {
|
|
@@ -13924,7 +13852,7 @@ const getHotUpdater = (distributionDomainName) => {
|
|
|
13924
13852
|
const hotUpdater = createHotUpdater({
|
|
13925
13853
|
database,
|
|
13926
13854
|
plugins,
|
|
13927
|
-
storage:
|
|
13855
|
+
storage: s3Storage({
|
|
13928
13856
|
bucketName: S3_BUCKET_NAME,
|
|
13929
13857
|
region: SSM_REGION,
|
|
13930
13858
|
getDownloadUrl: cloudFrontDownloadUrl({
|
|
@@ -13933,7 +13861,7 @@ const getHotUpdater = (distributionDomainName) => {
|
|
|
13933
13861
|
ssmParameterName: SSM_PARAMETER_NAME,
|
|
13934
13862
|
publicBaseUrl: `https://${distributionDomainName}`
|
|
13935
13863
|
})
|
|
13936
|
-
})
|
|
13864
|
+
})
|
|
13937
13865
|
});
|
|
13938
13866
|
hotUpdaterByDistribution.set(distributionDomainName, hotUpdater);
|
|
13939
13867
|
return hotUpdater;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/aws",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.44",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"es-toolkit": "^1.32.0",
|
|
46
46
|
"execa": "9.5.2",
|
|
47
47
|
"mime": "^4.0.4",
|
|
48
|
-
"@hot-updater/
|
|
49
|
-
"@hot-updater/
|
|
50
|
-
"@hot-updater/
|
|
48
|
+
"@hot-updater/protocol": "1.0.0-rc.44",
|
|
49
|
+
"@hot-updater/mock": "1.0.0-rc.44",
|
|
50
|
+
"@hot-updater/test-utils": "1.0.0-rc.44"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@aws-sdk/client-cloudfront": "3.1066.0",
|
|
@@ -61,10 +61,11 @@
|
|
|
61
61
|
"@aws-sdk/credential-providers": "3.1066.0",
|
|
62
62
|
"@aws-sdk/lib-storage": "3.1066.0",
|
|
63
63
|
"@aws-sdk/lib-dynamodb": "3.1066.0",
|
|
64
|
+
"@aws-sdk/s3-request-presigner": "3.1066.0",
|
|
64
65
|
"hono": "4.12.34",
|
|
65
|
-
"@hot-updater/
|
|
66
|
-
"@hot-updater/
|
|
67
|
-
"@hot-updater/server": "1.0.0-rc.
|
|
66
|
+
"@hot-updater/cli-tools": "1.0.0-rc.44",
|
|
67
|
+
"@hot-updater/plugin-core": "1.0.0-rc.44",
|
|
68
|
+
"@hot-updater/server": "1.0.0-rc.44"
|
|
68
69
|
},
|
|
69
70
|
"scripts": {
|
|
70
71
|
"build": "tsdown",
|