@hot-updater/aws 1.1.4 → 1.1.6
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 +8 -146
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +9 -147
- package/dist/lambda/index.cjs +84 -59
- package/package.json +8 -7
package/dist/index.cjs
CHANGED
|
@@ -5,8 +5,7 @@ let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
|
|
|
5
5
|
let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
6
6
|
let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
7
7
|
let _aws_sdk_cloudfront_signer = require("@aws-sdk/cloudfront-signer");
|
|
8
|
-
let
|
|
9
|
-
let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
|
|
8
|
+
let _hot_updater_storage_s3 = require("@hot-updater/storage-s3");
|
|
10
9
|
//#region src/cloudFrontInvalidation.ts
|
|
11
10
|
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
12
11
|
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
@@ -486,153 +485,16 @@ const cloudFrontDownloadUrl = (config) => {
|
|
|
486
485
|
};
|
|
487
486
|
//#endregion
|
|
488
487
|
//#region src/s3Storage.ts
|
|
489
|
-
/** Devices download right after the update check that returns the URL. */
|
|
490
|
-
const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
|
|
491
|
-
const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
|
|
492
488
|
function s3Storage(config) {
|
|
493
489
|
const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
|
|
494
|
-
|
|
495
|
-
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
496
|
-
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(normalizedBasePath);
|
|
497
|
-
const getListPrefix = (prefix = "") => {
|
|
498
|
-
const normalizedPrefix = prefix.replace(/^\/+|\/+$/g, "");
|
|
499
|
-
const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
|
|
500
|
-
return value ? `${value}/` : "";
|
|
501
|
-
};
|
|
502
|
-
/**
|
|
503
|
-
* The URI `put` returns for the key, or null for a key no `put` writes,
|
|
504
|
-
* such as a folder marker that ends in "/".
|
|
505
|
-
*/
|
|
506
|
-
const getListedStorageUri = (key) => {
|
|
507
|
-
try {
|
|
508
|
-
return (0, _hot_updater_plugin_core.createStorageUri)({
|
|
509
|
-
bucket: bucketName,
|
|
510
|
-
key,
|
|
511
|
-
protocol: "s3"
|
|
512
|
-
});
|
|
513
|
-
} catch {
|
|
514
|
-
return null;
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
const getRelativeKey = (key) => {
|
|
518
|
-
if (!normalizedBasePath) return key;
|
|
519
|
-
const basePrefix = `${normalizedBasePath}/`;
|
|
520
|
-
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
521
|
-
};
|
|
522
|
-
const parseAndValidate = (storageUri) => {
|
|
523
|
-
const parsed = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "s3");
|
|
524
|
-
if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
|
|
525
|
-
return parsed;
|
|
526
|
-
};
|
|
527
|
-
return (0, _hot_updater_plugin_core.createStorageAdapter)({
|
|
490
|
+
return (0, _hot_updater_storage_s3.createS3Storage)({
|
|
528
491
|
name: "s3Storage",
|
|
529
|
-
protocol: "s3"
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
Bucket: bucketName,
|
|
536
|
-
ContinuationToken: continuationToken,
|
|
537
|
-
Prefix: getListPrefix(prefix)
|
|
538
|
-
}));
|
|
539
|
-
for (const object of response.Contents ?? []) {
|
|
540
|
-
if (!object.Key) continue;
|
|
541
|
-
const storageUri = getListedStorageUri(object.Key);
|
|
542
|
-
if (storageUri === null) continue;
|
|
543
|
-
objects.push({
|
|
544
|
-
key: getRelativeKey(object.Key),
|
|
545
|
-
lastModifiedAt: object.LastModified,
|
|
546
|
-
size: object.Size ?? 0,
|
|
547
|
-
storageUri
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
continuationToken = response.NextContinuationToken;
|
|
551
|
-
} while (continuationToken);
|
|
552
|
-
return objects;
|
|
553
|
-
},
|
|
554
|
-
async deleteObjects(relativeKeys) {
|
|
555
|
-
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
556
|
-
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
557
|
-
const batch = keys.slice(offset, offset + 1e3);
|
|
558
|
-
if (batch.length === 0) continue;
|
|
559
|
-
const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
|
|
560
|
-
Bucket: bucketName,
|
|
561
|
-
Delete: {
|
|
562
|
-
Objects: batch.map((Key) => ({ Key })),
|
|
563
|
-
Quiet: true
|
|
564
|
-
}
|
|
565
|
-
}));
|
|
566
|
-
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
567
|
-
}
|
|
568
|
-
},
|
|
569
|
-
async put({ key, body, contentLength, contentType }) {
|
|
570
|
-
const storageKey = getStorageKey(key);
|
|
571
|
-
const response = await new _aws_sdk_lib_storage.Upload({
|
|
572
|
-
client,
|
|
573
|
-
params: {
|
|
574
|
-
Body: body,
|
|
575
|
-
Bucket: bucketName,
|
|
576
|
-
CacheControl: "max-age=31536000",
|
|
577
|
-
...contentLength === void 0 ? {} : { ContentLength: contentLength },
|
|
578
|
-
ContentType: contentType,
|
|
579
|
-
Key: storageKey
|
|
580
|
-
}
|
|
581
|
-
}).done();
|
|
582
|
-
if (!response.Bucket || !response.Key) throw new Error("Upload failed");
|
|
583
|
-
return { storageUri: (0, _hot_updater_plugin_core.createStorageUri)({
|
|
584
|
-
bucket: bucketName,
|
|
585
|
-
key: storageKey,
|
|
586
|
-
protocol: "s3"
|
|
587
|
-
}) };
|
|
588
|
-
},
|
|
589
|
-
async get({ storageUri }) {
|
|
590
|
-
const { key } = parseAndValidate(storageUri);
|
|
591
|
-
try {
|
|
592
|
-
const response = await client.send(new _aws_sdk_client_s3.GetObjectCommand({
|
|
593
|
-
Bucket: bucketName,
|
|
594
|
-
Key: key
|
|
595
|
-
}));
|
|
596
|
-
if (!response.Body) return { response: null };
|
|
597
|
-
const headers = new Headers();
|
|
598
|
-
if (response.ContentType) headers.set("content-type", response.ContentType);
|
|
599
|
-
if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
|
|
600
|
-
return { response: new Response(response.Body.transformToWebStream(), { headers }) };
|
|
601
|
-
} catch (error) {
|
|
602
|
-
if (isObjectNotFoundError(error)) return { response: null };
|
|
603
|
-
throw error;
|
|
604
|
-
}
|
|
605
|
-
},
|
|
606
|
-
async getDownloadUrl({ storageUri }) {
|
|
607
|
-
const { key } = parseAndValidate(storageUri);
|
|
608
|
-
if (getDownloadUrl) return getDownloadUrl({ storageUri });
|
|
609
|
-
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
610
|
-
return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
|
|
611
|
-
Bucket: bucketName,
|
|
612
|
-
Key: key
|
|
613
|
-
}), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
|
|
614
|
-
},
|
|
615
|
-
async exists({ storageUri }) {
|
|
616
|
-
const { key } = parseAndValidate(storageUri);
|
|
617
|
-
try {
|
|
618
|
-
await client.send(new _aws_sdk_client_s3.HeadObjectCommand({
|
|
619
|
-
Bucket: bucketName,
|
|
620
|
-
Key: key
|
|
621
|
-
}));
|
|
622
|
-
return { exists: true };
|
|
623
|
-
} catch (error) {
|
|
624
|
-
if (isObjectNotFoundError(error)) return { exists: false };
|
|
625
|
-
throw error;
|
|
626
|
-
}
|
|
627
|
-
},
|
|
628
|
-
async delete({ storageUri }) {
|
|
629
|
-
const { key } = parseAndValidate(storageUri);
|
|
630
|
-
await client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
|
|
631
|
-
Bucket: bucketName,
|
|
632
|
-
Key: key
|
|
633
|
-
}));
|
|
634
|
-
return { deleted: true };
|
|
635
|
-
}
|
|
492
|
+
protocol: "s3"
|
|
493
|
+
}, {
|
|
494
|
+
...applyS3RuntimeAwsConfig(s3Config),
|
|
495
|
+
bucketName,
|
|
496
|
+
basePath,
|
|
497
|
+
getDownloadUrl
|
|
636
498
|
});
|
|
637
499
|
}
|
|
638
500
|
//#endregion
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
|
|
2
2
|
import { EngineDatabase, PluginTables, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
3
3
|
import { S3ClientConfig } from "@aws-sdk/client-s3";
|
|
4
|
+
import { S3StorageOperations } from "@hot-updater/storage-s3";
|
|
4
5
|
//#region src/dynamoDB.d.ts
|
|
5
6
|
interface DynamoDBConfig extends DynamoDBClientConfig {
|
|
6
7
|
readonly apiBasePath?: string;
|
|
@@ -70,7 +71,6 @@ interface S3StorageConfig extends S3ClientConfig {
|
|
|
70
71
|
*/
|
|
71
72
|
getDownloadUrl?: StorageAdapter["getDownloadUrl"];
|
|
72
73
|
}
|
|
73
|
-
type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
74
74
|
declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
|
|
75
75
|
//#endregion
|
|
76
76
|
export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig, cloudFrontDownloadUrl, dynamoDB, migrateDynamoDB, s3Storage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DynamoDBClientConfig } from "@aws-sdk/client-dynamodb";
|
|
2
2
|
import { EngineDatabase, PluginTables, StorageAdapter, StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
3
|
+
import { S3StorageOperations } from "@hot-updater/storage-s3";
|
|
3
4
|
import { S3ClientConfig } from "@aws-sdk/client-s3";
|
|
4
5
|
//#region src/dynamoDB.d.ts
|
|
5
6
|
interface DynamoDBConfig extends DynamoDBClientConfig {
|
|
@@ -70,7 +71,6 @@ interface S3StorageConfig extends S3ClientConfig {
|
|
|
70
71
|
*/
|
|
71
72
|
getDownloadUrl?: StorageAdapter["getDownloadUrl"];
|
|
72
73
|
}
|
|
73
|
-
type S3StorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
74
74
|
declare function s3Storage(config: S3StorageConfig): StorageAdapterWith<S3StorageOperations>;
|
|
75
75
|
//#endregion
|
|
76
76
|
export { CloudFrontDownloadUrlOptions, type DynamoDBConfig, S3StorageConfig, cloudFrontDownloadUrl, dynamoDB, migrateDynamoDB, s3Storage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { CreateTableCommand, DescribeTableCommand, DescribeTimeToLiveCommand, DynamoDBClient, UpdateTimeToLiveCommand, waitUntilTableExists } from "@aws-sdk/client-dynamodb";
|
|
2
|
-
import { BUNDLE_STORAGE_PREFIX, CONTENT_ADDRESSED_ASSET_PREFIX, createEngineDatabase, createKvAdapter,
|
|
2
|
+
import { BUNDLE_STORAGE_PREFIX, CONTENT_ADDRESSED_ASSET_PREFIX, createEngineDatabase, createKvAdapter, migrateCoreSchema } 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
|
-
import {
|
|
8
|
-
import { Upload } from "@aws-sdk/lib-storage";
|
|
7
|
+
import { createS3Storage } from "@hot-updater/storage-s3";
|
|
9
8
|
//#region src/cloudFrontInvalidation.ts
|
|
10
9
|
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
11
10
|
const DEFAULT_TIMEOUT_MS = 3e5;
|
|
@@ -485,153 +484,16 @@ const cloudFrontDownloadUrl = (config) => {
|
|
|
485
484
|
};
|
|
486
485
|
//#endregion
|
|
487
486
|
//#region src/s3Storage.ts
|
|
488
|
-
/** Devices download right after the update check that returns the URL. */
|
|
489
|
-
const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
|
|
490
|
-
const isObjectNotFoundError = (error) => error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey");
|
|
491
487
|
function s3Storage(config) {
|
|
492
488
|
const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
|
|
493
|
-
|
|
494
|
-
const normalizedBasePath = basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
495
|
-
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
496
|
-
const getListPrefix = (prefix = "") => {
|
|
497
|
-
const normalizedPrefix = prefix.replace(/^\/+|\/+$/g, "");
|
|
498
|
-
const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
|
|
499
|
-
return value ? `${value}/` : "";
|
|
500
|
-
};
|
|
501
|
-
/**
|
|
502
|
-
* The URI `put` returns for the key, or null for a key no `put` writes,
|
|
503
|
-
* such as a folder marker that ends in "/".
|
|
504
|
-
*/
|
|
505
|
-
const getListedStorageUri = (key) => {
|
|
506
|
-
try {
|
|
507
|
-
return createStorageUri({
|
|
508
|
-
bucket: bucketName,
|
|
509
|
-
key,
|
|
510
|
-
protocol: "s3"
|
|
511
|
-
});
|
|
512
|
-
} catch {
|
|
513
|
-
return null;
|
|
514
|
-
}
|
|
515
|
-
};
|
|
516
|
-
const getRelativeKey = (key) => {
|
|
517
|
-
if (!normalizedBasePath) return key;
|
|
518
|
-
const basePrefix = `${normalizedBasePath}/`;
|
|
519
|
-
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
520
|
-
};
|
|
521
|
-
const parseAndValidate = (storageUri) => {
|
|
522
|
-
const parsed = parseStorageUri(storageUri, "s3");
|
|
523
|
-
if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
|
|
524
|
-
return parsed;
|
|
525
|
-
};
|
|
526
|
-
return createStorageAdapter({
|
|
489
|
+
return createS3Storage({
|
|
527
490
|
name: "s3Storage",
|
|
528
|
-
protocol: "s3"
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
Bucket: bucketName,
|
|
535
|
-
ContinuationToken: continuationToken,
|
|
536
|
-
Prefix: getListPrefix(prefix)
|
|
537
|
-
}));
|
|
538
|
-
for (const object of response.Contents ?? []) {
|
|
539
|
-
if (!object.Key) continue;
|
|
540
|
-
const storageUri = getListedStorageUri(object.Key);
|
|
541
|
-
if (storageUri === null) continue;
|
|
542
|
-
objects.push({
|
|
543
|
-
key: getRelativeKey(object.Key),
|
|
544
|
-
lastModifiedAt: object.LastModified,
|
|
545
|
-
size: object.Size ?? 0,
|
|
546
|
-
storageUri
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
continuationToken = response.NextContinuationToken;
|
|
550
|
-
} while (continuationToken);
|
|
551
|
-
return objects;
|
|
552
|
-
},
|
|
553
|
-
async deleteObjects(relativeKeys) {
|
|
554
|
-
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
555
|
-
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
556
|
-
const batch = keys.slice(offset, offset + 1e3);
|
|
557
|
-
if (batch.length === 0) continue;
|
|
558
|
-
const response = await client.send(new DeleteObjectsCommand({
|
|
559
|
-
Bucket: bucketName,
|
|
560
|
-
Delete: {
|
|
561
|
-
Objects: batch.map((Key) => ({ Key })),
|
|
562
|
-
Quiet: true
|
|
563
|
-
}
|
|
564
|
-
}));
|
|
565
|
-
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
async put({ key, body, contentLength, contentType }) {
|
|
569
|
-
const storageKey = getStorageKey(key);
|
|
570
|
-
const response = await new Upload({
|
|
571
|
-
client,
|
|
572
|
-
params: {
|
|
573
|
-
Body: body,
|
|
574
|
-
Bucket: bucketName,
|
|
575
|
-
CacheControl: "max-age=31536000",
|
|
576
|
-
...contentLength === void 0 ? {} : { ContentLength: contentLength },
|
|
577
|
-
ContentType: contentType,
|
|
578
|
-
Key: storageKey
|
|
579
|
-
}
|
|
580
|
-
}).done();
|
|
581
|
-
if (!response.Bucket || !response.Key) throw new Error("Upload failed");
|
|
582
|
-
return { storageUri: createStorageUri({
|
|
583
|
-
bucket: bucketName,
|
|
584
|
-
key: storageKey,
|
|
585
|
-
protocol: "s3"
|
|
586
|
-
}) };
|
|
587
|
-
},
|
|
588
|
-
async get({ storageUri }) {
|
|
589
|
-
const { key } = parseAndValidate(storageUri);
|
|
590
|
-
try {
|
|
591
|
-
const response = await client.send(new GetObjectCommand({
|
|
592
|
-
Bucket: bucketName,
|
|
593
|
-
Key: key
|
|
594
|
-
}));
|
|
595
|
-
if (!response.Body) return { response: null };
|
|
596
|
-
const headers = new Headers();
|
|
597
|
-
if (response.ContentType) headers.set("content-type", response.ContentType);
|
|
598
|
-
if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
|
|
599
|
-
return { response: new Response(response.Body.transformToWebStream(), { headers }) };
|
|
600
|
-
} catch (error) {
|
|
601
|
-
if (isObjectNotFoundError(error)) return { response: null };
|
|
602
|
-
throw error;
|
|
603
|
-
}
|
|
604
|
-
},
|
|
605
|
-
async getDownloadUrl({ storageUri }) {
|
|
606
|
-
const { key } = parseAndValidate(storageUri);
|
|
607
|
-
if (getDownloadUrl) return getDownloadUrl({ storageUri });
|
|
608
|
-
const { getSignedUrl } = await import("@aws-sdk/s3-request-presigner");
|
|
609
|
-
return { url: await getSignedUrl(client, new GetObjectCommand({
|
|
610
|
-
Bucket: bucketName,
|
|
611
|
-
Key: key
|
|
612
|
-
}), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
|
|
613
|
-
},
|
|
614
|
-
async exists({ storageUri }) {
|
|
615
|
-
const { key } = parseAndValidate(storageUri);
|
|
616
|
-
try {
|
|
617
|
-
await client.send(new HeadObjectCommand({
|
|
618
|
-
Bucket: bucketName,
|
|
619
|
-
Key: key
|
|
620
|
-
}));
|
|
621
|
-
return { exists: true };
|
|
622
|
-
} catch (error) {
|
|
623
|
-
if (isObjectNotFoundError(error)) return { exists: false };
|
|
624
|
-
throw error;
|
|
625
|
-
}
|
|
626
|
-
},
|
|
627
|
-
async delete({ storageUri }) {
|
|
628
|
-
const { key } = parseAndValidate(storageUri);
|
|
629
|
-
await client.send(new DeleteObjectCommand({
|
|
630
|
-
Bucket: bucketName,
|
|
631
|
-
Key: key
|
|
632
|
-
}));
|
|
633
|
-
return { deleted: true };
|
|
634
|
-
}
|
|
491
|
+
protocol: "s3"
|
|
492
|
+
}, {
|
|
493
|
+
...applyS3RuntimeAwsConfig(s3Config),
|
|
494
|
+
bucketName,
|
|
495
|
+
basePath,
|
|
496
|
+
getDownloadUrl
|
|
635
497
|
});
|
|
636
498
|
}
|
|
637
499
|
//#endregion
|
package/dist/lambda/index.cjs
CHANGED
|
@@ -30,6 +30,7 @@ let _aws_sdk_client_cloudfront = require("@aws-sdk/client-cloudfront");
|
|
|
30
30
|
let _aws_sdk_lib_dynamodb = require("@aws-sdk/lib-dynamodb");
|
|
31
31
|
let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
32
32
|
let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
|
|
33
|
+
let _aws_sdk_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
|
|
33
34
|
const SHA256_FILE_HASH_RE = /^[0-9a-f]{64}$/;
|
|
34
35
|
const isContentAddressedAssetFileHash = (value) => {
|
|
35
36
|
return typeof value === "string" && SHA256_FILE_HASH_RE.test(value);
|
|
@@ -4549,7 +4550,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
|
|
|
4549
4550
|
};
|
|
4550
4551
|
//#endregion
|
|
4551
4552
|
//#region ../../packages/server/dist/version.mjs
|
|
4552
|
-
const HOT_UPDATER_SERVER_VERSION = "1.1.
|
|
4553
|
+
const HOT_UPDATER_SERVER_VERSION = "1.1.6";
|
|
4553
4554
|
//#endregion
|
|
4554
4555
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
4555
4556
|
/**
|
|
@@ -13829,18 +13830,31 @@ const dynamoDB = (config) => {
|
|
|
13829
13830
|
};
|
|
13830
13831
|
};
|
|
13831
13832
|
//#endregion
|
|
13832
|
-
//#region
|
|
13833
|
+
//#region ../storage-s3/dist/index.mjs
|
|
13833
13834
|
/** Devices download right after the update check that returns the URL. */
|
|
13834
|
-
const
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
|
|
13839
|
-
|
|
13835
|
+
const DEFAULT_SIGNED_LIFETIME_SECONDS = 3600;
|
|
13836
|
+
/** SigV4's limit on a presigned URL. */
|
|
13837
|
+
const MAX_SIGNED_LIFETIME_SECONDS = 604800;
|
|
13838
|
+
/** DeleteObjects takes at most 1000 keys a request. */
|
|
13839
|
+
const DELETE_BATCH_SIZE = 1e3;
|
|
13840
|
+
const isObjectNotFoundError = (error) => {
|
|
13841
|
+
if (error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey")) return true;
|
|
13842
|
+
return typeof error === "object" && error !== null && "$metadata" in error && error.$metadata?.httpStatusCode === 404;
|
|
13843
|
+
};
|
|
13844
|
+
const trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
|
|
13845
|
+
/**
|
|
13846
|
+
* A storage adapter over a bucket's S3 API, which AWS S3, Cloudflare R2, and
|
|
13847
|
+
* Neon Object Storage all serve. Each provider package passes its own name,
|
|
13848
|
+
* URI protocol, and client defaults.
|
|
13849
|
+
*/
|
|
13850
|
+
const createS3Storage = ({ name, protocol }, config) => {
|
|
13851
|
+
const { bucketName, basePath, getDownloadUrl, signedUrlExpiresIn = DEFAULT_SIGNED_LIFETIME_SECONDS, ...clientConfig } = config;
|
|
13852
|
+
if (!Number.isFinite(signedUrlExpiresIn) || signedUrlExpiresIn <= 0 || signedUrlExpiresIn > MAX_SIGNED_LIFETIME_SECONDS) throw new Error(`${name} signedUrlExpiresIn must be between 1 and ${MAX_SIGNED_LIFETIME_SECONDS} seconds (seven days).`);
|
|
13853
|
+
const client = new _aws_sdk_client_s3.S3Client(clientConfig);
|
|
13854
|
+
const normalizedBasePath = trimSlashes(basePath ?? "");
|
|
13840
13855
|
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
13841
13856
|
const getListPrefix = (prefix = "") => {
|
|
13842
|
-
const
|
|
13843
|
-
const value = [normalizedBasePath, normalizedPrefix].filter(Boolean).join("/");
|
|
13857
|
+
const value = [normalizedBasePath, trimSlashes(prefix)].filter(Boolean).join("/");
|
|
13844
13858
|
return value ? `${value}/` : "";
|
|
13845
13859
|
};
|
|
13846
13860
|
/**
|
|
@@ -13852,7 +13866,7 @@ function s3Storage(config) {
|
|
|
13852
13866
|
return createStorageUri({
|
|
13853
13867
|
bucket: bucketName,
|
|
13854
13868
|
key,
|
|
13855
|
-
protocol
|
|
13869
|
+
protocol
|
|
13856
13870
|
});
|
|
13857
13871
|
} catch {
|
|
13858
13872
|
return null;
|
|
@@ -13864,55 +13878,16 @@ function s3Storage(config) {
|
|
|
13864
13878
|
return key.startsWith(basePrefix) ? key.slice(basePrefix.length) : key;
|
|
13865
13879
|
};
|
|
13866
13880
|
const parseAndValidate = (storageUri) => {
|
|
13867
|
-
const parsed = parseStorageUri(storageUri,
|
|
13881
|
+
const parsed = parseStorageUri(storageUri, protocol);
|
|
13868
13882
|
if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
|
|
13869
13883
|
return parsed;
|
|
13870
13884
|
};
|
|
13871
13885
|
return createStorageAdapter({
|
|
13872
|
-
name
|
|
13873
|
-
protocol
|
|
13874
|
-
async listObjects(prefix) {
|
|
13875
|
-
const objects = [];
|
|
13876
|
-
let continuationToken;
|
|
13877
|
-
do {
|
|
13878
|
-
const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
|
|
13879
|
-
Bucket: bucketName,
|
|
13880
|
-
ContinuationToken: continuationToken,
|
|
13881
|
-
Prefix: getListPrefix(prefix)
|
|
13882
|
-
}));
|
|
13883
|
-
for (const object of response.Contents ?? []) {
|
|
13884
|
-
if (!object.Key) continue;
|
|
13885
|
-
const storageUri = getListedStorageUri(object.Key);
|
|
13886
|
-
if (storageUri === null) continue;
|
|
13887
|
-
objects.push({
|
|
13888
|
-
key: getRelativeKey(object.Key),
|
|
13889
|
-
lastModifiedAt: object.LastModified,
|
|
13890
|
-
size: object.Size ?? 0,
|
|
13891
|
-
storageUri
|
|
13892
|
-
});
|
|
13893
|
-
}
|
|
13894
|
-
continuationToken = response.NextContinuationToken;
|
|
13895
|
-
} while (continuationToken);
|
|
13896
|
-
return objects;
|
|
13897
|
-
},
|
|
13898
|
-
async deleteObjects(relativeKeys) {
|
|
13899
|
-
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
13900
|
-
for (let offset = 0; offset < keys.length; offset += 1e3) {
|
|
13901
|
-
const batch = keys.slice(offset, offset + 1e3);
|
|
13902
|
-
if (batch.length === 0) continue;
|
|
13903
|
-
const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
|
|
13904
|
-
Bucket: bucketName,
|
|
13905
|
-
Delete: {
|
|
13906
|
-
Objects: batch.map((Key) => ({ Key })),
|
|
13907
|
-
Quiet: true
|
|
13908
|
-
}
|
|
13909
|
-
}));
|
|
13910
|
-
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s).`);
|
|
13911
|
-
}
|
|
13912
|
-
},
|
|
13886
|
+
name,
|
|
13887
|
+
protocol,
|
|
13913
13888
|
async put({ key, body, contentLength, contentType }) {
|
|
13914
13889
|
const storageKey = getStorageKey(key);
|
|
13915
|
-
|
|
13890
|
+
await new _aws_sdk_lib_storage.Upload({
|
|
13916
13891
|
client,
|
|
13917
13892
|
params: {
|
|
13918
13893
|
Body: body,
|
|
@@ -13923,11 +13898,10 @@ function s3Storage(config) {
|
|
|
13923
13898
|
Key: storageKey
|
|
13924
13899
|
}
|
|
13925
13900
|
}).done();
|
|
13926
|
-
if (!response.Bucket || !response.Key) throw new Error("Upload failed");
|
|
13927
13901
|
return { storageUri: createStorageUri({
|
|
13928
13902
|
bucket: bucketName,
|
|
13929
13903
|
key: storageKey,
|
|
13930
|
-
protocol
|
|
13904
|
+
protocol
|
|
13931
13905
|
}) };
|
|
13932
13906
|
},
|
|
13933
13907
|
async get({ storageUri }) {
|
|
@@ -13950,11 +13924,10 @@ function s3Storage(config) {
|
|
|
13950
13924
|
async getDownloadUrl({ storageUri }) {
|
|
13951
13925
|
const { key } = parseAndValidate(storageUri);
|
|
13952
13926
|
if (getDownloadUrl) return getDownloadUrl({ storageUri });
|
|
13953
|
-
|
|
13954
|
-
return { url: await getSignedUrl(client, new _aws_sdk_client_s3.GetObjectCommand({
|
|
13927
|
+
return { url: await (0, _aws_sdk_s3_request_presigner.getSignedUrl)(client, new _aws_sdk_client_s3.GetObjectCommand({
|
|
13955
13928
|
Bucket: bucketName,
|
|
13956
13929
|
Key: key
|
|
13957
|
-
}), { expiresIn:
|
|
13930
|
+
}), { expiresIn: signedUrlExpiresIn }) };
|
|
13958
13931
|
},
|
|
13959
13932
|
async exists({ storageUri }) {
|
|
13960
13933
|
const { key } = parseAndValidate(storageUri);
|
|
@@ -13976,8 +13949,60 @@ function s3Storage(config) {
|
|
|
13976
13949
|
Key: key
|
|
13977
13950
|
}));
|
|
13978
13951
|
return { deleted: true };
|
|
13952
|
+
},
|
|
13953
|
+
async listObjects(prefix) {
|
|
13954
|
+
const objects = [];
|
|
13955
|
+
let continuationToken;
|
|
13956
|
+
do {
|
|
13957
|
+
const response = await client.send(new _aws_sdk_client_s3.ListObjectsV2Command({
|
|
13958
|
+
Bucket: bucketName,
|
|
13959
|
+
ContinuationToken: continuationToken,
|
|
13960
|
+
Prefix: getListPrefix(prefix)
|
|
13961
|
+
}));
|
|
13962
|
+
for (const object of response.Contents ?? []) {
|
|
13963
|
+
if (!object.Key) continue;
|
|
13964
|
+
const storageUri = getListedStorageUri(object.Key);
|
|
13965
|
+
if (storageUri === null) continue;
|
|
13966
|
+
objects.push({
|
|
13967
|
+
key: getRelativeKey(object.Key),
|
|
13968
|
+
lastModifiedAt: object.LastModified,
|
|
13969
|
+
size: object.Size ?? 0,
|
|
13970
|
+
storageUri
|
|
13971
|
+
});
|
|
13972
|
+
}
|
|
13973
|
+
continuationToken = response.NextContinuationToken;
|
|
13974
|
+
} while (continuationToken);
|
|
13975
|
+
return objects;
|
|
13976
|
+
},
|
|
13977
|
+
async deleteObjects(relativeKeys) {
|
|
13978
|
+
const keys = relativeKeys.map((key) => getStorageKey(key));
|
|
13979
|
+
for (let offset = 0; offset < keys.length; offset += DELETE_BATCH_SIZE) {
|
|
13980
|
+
const batch = keys.slice(offset, offset + DELETE_BATCH_SIZE);
|
|
13981
|
+
const response = await client.send(new _aws_sdk_client_s3.DeleteObjectsCommand({
|
|
13982
|
+
Bucket: bucketName,
|
|
13983
|
+
Delete: {
|
|
13984
|
+
Objects: batch.map((Key) => ({ Key })),
|
|
13985
|
+
Quiet: true
|
|
13986
|
+
}
|
|
13987
|
+
}));
|
|
13988
|
+
if (response.Errors && response.Errors.length > 0) throw new Error(`Failed to delete ${response.Errors.length} S3 object(s) from bucket "${bucketName}".`);
|
|
13989
|
+
}
|
|
13979
13990
|
}
|
|
13980
13991
|
});
|
|
13992
|
+
};
|
|
13993
|
+
//#endregion
|
|
13994
|
+
//#region src/s3Storage.ts
|
|
13995
|
+
function s3Storage(config) {
|
|
13996
|
+
const { bucketName, basePath, getDownloadUrl, ...s3Config } = config;
|
|
13997
|
+
return createS3Storage({
|
|
13998
|
+
name: "s3Storage",
|
|
13999
|
+
protocol: "s3"
|
|
14000
|
+
}, {
|
|
14001
|
+
...applyS3RuntimeAwsConfig(s3Config),
|
|
14002
|
+
bucketName,
|
|
14003
|
+
basePath,
|
|
14004
|
+
getDownloadUrl
|
|
14005
|
+
});
|
|
13981
14006
|
}
|
|
13982
14007
|
//#endregion
|
|
13983
14008
|
//#region lambda/index.ts
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/aws",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.1.
|
|
4
|
+
"version": "1.1.6",
|
|
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/protocol": "1.1.
|
|
49
|
-
"@hot-updater/mock": "1.1.
|
|
50
|
-
"@hot-updater/test-utils": "1.1.
|
|
48
|
+
"@hot-updater/protocol": "1.1.6",
|
|
49
|
+
"@hot-updater/mock": "1.1.6",
|
|
50
|
+
"@hot-updater/test-utils": "1.1.6"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@aws-sdk/client-cloudfront": "3.1066.0",
|
|
@@ -63,9 +63,10 @@
|
|
|
63
63
|
"@aws-sdk/lib-dynamodb": "3.1066.0",
|
|
64
64
|
"@aws-sdk/s3-request-presigner": "3.1066.0",
|
|
65
65
|
"hono": "4.12.34",
|
|
66
|
-
"@hot-updater/cli-tools": "1.1.
|
|
67
|
-
"@hot-updater/plugin-core": "1.1.
|
|
68
|
-
"@hot-updater/server": "1.1.
|
|
66
|
+
"@hot-updater/cli-tools": "1.1.6",
|
|
67
|
+
"@hot-updater/plugin-core": "1.1.6",
|
|
68
|
+
"@hot-updater/server": "1.1.6",
|
|
69
|
+
"@hot-updater/storage-s3": "1.1.6"
|
|
69
70
|
},
|
|
70
71
|
"scripts": {
|
|
71
72
|
"build": "tsdown",
|