@hot-updater/aws 1.0.0-rc.2 → 1.0.0-rc.4
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/iac/index.cjs +121 -104
- package/dist/iac/index.d.cts +92 -1
- package/dist/iac/index.d.mts +93 -2
- package/dist/iac/index.mjs +116 -105
- package/dist/index.cjs +5 -5
- package/dist/index.mjs +5 -5
- package/dist/lambda/index.cjs +19 -21
- package/package.json +6 -6
package/dist/iac/index.cjs
CHANGED
|
@@ -7839,10 +7839,10 @@ const hasValidBundleEventShape = (value) => {
|
|
|
7839
7839
|
const type = field(value, "type");
|
|
7840
7840
|
const fromBundleId = field(value, "from_bundle_id");
|
|
7841
7841
|
const updateStrategy = field(value, "update_strategy");
|
|
7842
|
-
return (type === "UPDATE_APPLIED" || type === "RECOVERED"
|
|
7842
|
+
return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
|
|
7843
7843
|
};
|
|
7844
7844
|
const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
|
|
7845
|
-
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "
|
|
7845
|
+
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
|
|
7846
7846
|
const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
|
|
7847
7847
|
const parseOfficialRowItem = (value, partition, isRow) => {
|
|
7848
7848
|
if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
|
|
@@ -7864,7 +7864,7 @@ const insightsBundlePartition = (filter) => {
|
|
|
7864
7864
|
]);
|
|
7865
7865
|
return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${(0, node_crypto.createHash)("sha256").update(scope, "utf8").digest("hex")}`;
|
|
7866
7866
|
};
|
|
7867
|
-
const toInsightsBundleItem = (row) =>
|
|
7867
|
+
const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
|
|
7868
7868
|
pk: insightsBundlePartition({
|
|
7869
7869
|
platform: row.platform,
|
|
7870
7870
|
channel: row.channel,
|
|
@@ -7950,10 +7950,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
|
|
|
7950
7950
|
TableName: store.tableName,
|
|
7951
7951
|
Item: eventItem
|
|
7952
7952
|
} },
|
|
7953
|
-
|
|
7953
|
+
{ Put: {
|
|
7954
7954
|
TableName: store.tableName,
|
|
7955
7955
|
Item: bundleItem
|
|
7956
|
-
} }
|
|
7956
|
+
} }
|
|
7957
7957
|
];
|
|
7958
7958
|
if (current === null || advancesInsightsInstallation(next, current.row)) {
|
|
7959
7959
|
actions.push({ Put: {
|
|
@@ -9437,6 +9437,41 @@ const hasExpectedSchema = (table) => {
|
|
|
9437
9437
|
return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
|
|
9438
9438
|
};
|
|
9439
9439
|
const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
|
|
9440
|
+
const buildDynamoDBCreateTableInput = (tableName) => ({
|
|
9441
|
+
AttributeDefinitions: [
|
|
9442
|
+
{
|
|
9443
|
+
AttributeName: "pk",
|
|
9444
|
+
AttributeType: "S"
|
|
9445
|
+
},
|
|
9446
|
+
{
|
|
9447
|
+
AttributeName: "sk",
|
|
9448
|
+
AttributeType: "S"
|
|
9449
|
+
},
|
|
9450
|
+
{
|
|
9451
|
+
AttributeName: "gsi1pk",
|
|
9452
|
+
AttributeType: "S"
|
|
9453
|
+
},
|
|
9454
|
+
{
|
|
9455
|
+
AttributeName: "gsi1sk",
|
|
9456
|
+
AttributeType: "S"
|
|
9457
|
+
}
|
|
9458
|
+
],
|
|
9459
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
9460
|
+
DeletionProtectionEnabled: true,
|
|
9461
|
+
GlobalSecondaryIndexes: [{
|
|
9462
|
+
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9463
|
+
KeySchema: [...updateIndexKeySchema],
|
|
9464
|
+
Projection: { ProjectionType: "ALL" },
|
|
9465
|
+
OnDemandThroughput: onDemandThroughput
|
|
9466
|
+
}],
|
|
9467
|
+
KeySchema: [...primaryKeySchema],
|
|
9468
|
+
OnDemandThroughput: onDemandThroughput,
|
|
9469
|
+
TableName: tableName
|
|
9470
|
+
});
|
|
9471
|
+
const buildDynamoDBBackupInput = (tableName) => ({
|
|
9472
|
+
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9473
|
+
TableName: tableName
|
|
9474
|
+
});
|
|
9440
9475
|
var DynamoDBManager = class {
|
|
9441
9476
|
region;
|
|
9442
9477
|
client;
|
|
@@ -9457,37 +9492,7 @@ var DynamoDBManager = class {
|
|
|
9457
9492
|
if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
|
|
9458
9493
|
if (!isResourceNotFound(error)) throw error;
|
|
9459
9494
|
}
|
|
9460
|
-
await this.client.createTable(
|
|
9461
|
-
AttributeDefinitions: [
|
|
9462
|
-
{
|
|
9463
|
-
AttributeName: "pk",
|
|
9464
|
-
AttributeType: "S"
|
|
9465
|
-
},
|
|
9466
|
-
{
|
|
9467
|
-
AttributeName: "sk",
|
|
9468
|
-
AttributeType: "S"
|
|
9469
|
-
},
|
|
9470
|
-
{
|
|
9471
|
-
AttributeName: "gsi1pk",
|
|
9472
|
-
AttributeType: "S"
|
|
9473
|
-
},
|
|
9474
|
-
{
|
|
9475
|
-
AttributeName: "gsi1sk",
|
|
9476
|
-
AttributeType: "S"
|
|
9477
|
-
}
|
|
9478
|
-
],
|
|
9479
|
-
BillingMode: "PAY_PER_REQUEST",
|
|
9480
|
-
DeletionProtectionEnabled: true,
|
|
9481
|
-
GlobalSecondaryIndexes: [{
|
|
9482
|
-
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9483
|
-
KeySchema: [...updateIndexKeySchema],
|
|
9484
|
-
Projection: { ProjectionType: "ALL" },
|
|
9485
|
-
OnDemandThroughput: onDemandThroughput
|
|
9486
|
-
}],
|
|
9487
|
-
KeySchema: [...primaryKeySchema],
|
|
9488
|
-
OnDemandThroughput: onDemandThroughput,
|
|
9489
|
-
TableName: tableName
|
|
9490
|
-
});
|
|
9495
|
+
await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
|
|
9491
9496
|
await (0, _aws_sdk_client_dynamodb.waitUntilTableExists)({
|
|
9492
9497
|
client: this.client,
|
|
9493
9498
|
maxWaitTime: 120
|
|
@@ -9497,14 +9502,80 @@ var DynamoDBManager = class {
|
|
|
9497
9502
|
async ensureLifecycle(tableName) {
|
|
9498
9503
|
const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
|
|
9499
9504
|
if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
|
|
9500
|
-
await this.client.updateContinuousBackups(
|
|
9501
|
-
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9502
|
-
TableName: tableName
|
|
9503
|
-
});
|
|
9505
|
+
await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
|
|
9504
9506
|
}
|
|
9505
9507
|
};
|
|
9506
9508
|
//#endregion
|
|
9507
9509
|
//#region iac/iam.ts
|
|
9510
|
+
const buildDynamoDBPolicy = (region, accountId, tableName) => {
|
|
9511
|
+
const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
|
|
9512
|
+
return {
|
|
9513
|
+
Version: "2012-10-17",
|
|
9514
|
+
Statement: [{
|
|
9515
|
+
Action: ["dynamodb:Query"],
|
|
9516
|
+
Effect: "Allow",
|
|
9517
|
+
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9518
|
+
}, {
|
|
9519
|
+
Action: [
|
|
9520
|
+
"dynamodb:BatchGetItem",
|
|
9521
|
+
"dynamodb:DeleteItem",
|
|
9522
|
+
"dynamodb:GetItem",
|
|
9523
|
+
"dynamodb:PutItem",
|
|
9524
|
+
"dynamodb:Query",
|
|
9525
|
+
"dynamodb:TransactWriteItems",
|
|
9526
|
+
"dynamodb:UpdateItem"
|
|
9527
|
+
],
|
|
9528
|
+
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9529
|
+
"_hot-updater",
|
|
9530
|
+
"bundles",
|
|
9531
|
+
"bundle_patches",
|
|
9532
|
+
DYNAMODB_CHANNEL_PARTITION,
|
|
9533
|
+
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9534
|
+
DYNAMODB_INSIGHTS_PARTITION,
|
|
9535
|
+
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9536
|
+
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9537
|
+
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9538
|
+
"_hot-updater#insights-user#*",
|
|
9539
|
+
DYNAMODB_API_KEY_PARTITION,
|
|
9540
|
+
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9541
|
+
] } },
|
|
9542
|
+
Effect: "Allow",
|
|
9543
|
+
Resource: [tableArn]
|
|
9544
|
+
}]
|
|
9545
|
+
};
|
|
9546
|
+
};
|
|
9547
|
+
const buildS3Policy = (bucketName) => {
|
|
9548
|
+
return {
|
|
9549
|
+
Version: "2012-10-17",
|
|
9550
|
+
Statement: [{
|
|
9551
|
+
Action: ["s3:ListBucket"],
|
|
9552
|
+
Effect: "Allow",
|
|
9553
|
+
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9554
|
+
}, {
|
|
9555
|
+
Action: ["s3:GetObject"],
|
|
9556
|
+
Effect: "Allow",
|
|
9557
|
+
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9558
|
+
}]
|
|
9559
|
+
};
|
|
9560
|
+
};
|
|
9561
|
+
const buildSsmPolicy = (region, accountId, parameterName) => {
|
|
9562
|
+
return {
|
|
9563
|
+
Version: "2012-10-17",
|
|
9564
|
+
Statement: [{
|
|
9565
|
+
Effect: "Allow",
|
|
9566
|
+
Action: ["ssm:GetParameter"],
|
|
9567
|
+
Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
|
|
9568
|
+
}]
|
|
9569
|
+
};
|
|
9570
|
+
};
|
|
9571
|
+
const LAMBDA_EDGE_TRUST_POLICY = {
|
|
9572
|
+
Version: "2012-10-17",
|
|
9573
|
+
Statement: [{
|
|
9574
|
+
Effect: "Allow",
|
|
9575
|
+
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9576
|
+
Action: "sts:AssumeRole"
|
|
9577
|
+
}]
|
|
9578
|
+
};
|
|
9508
9579
|
var IAMManager = class {
|
|
9509
9580
|
region;
|
|
9510
9581
|
credentials;
|
|
@@ -9521,75 +9592,22 @@ var IAMManager = class {
|
|
|
9521
9592
|
});
|
|
9522
9593
|
}
|
|
9523
9594
|
async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
|
|
9524
|
-
const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
|
|
9525
9595
|
await iamClient.putRolePolicy({
|
|
9526
|
-
PolicyDocument: JSON.stringify(
|
|
9527
|
-
Version: "2012-10-17",
|
|
9528
|
-
Statement: [{
|
|
9529
|
-
Action: ["dynamodb:Query"],
|
|
9530
|
-
Effect: "Allow",
|
|
9531
|
-
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9532
|
-
}, {
|
|
9533
|
-
Action: [
|
|
9534
|
-
"dynamodb:BatchGetItem",
|
|
9535
|
-
"dynamodb:DeleteItem",
|
|
9536
|
-
"dynamodb:GetItem",
|
|
9537
|
-
"dynamodb:PutItem",
|
|
9538
|
-
"dynamodb:Query",
|
|
9539
|
-
"dynamodb:TransactWriteItems",
|
|
9540
|
-
"dynamodb:UpdateItem"
|
|
9541
|
-
],
|
|
9542
|
-
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9543
|
-
"_hot-updater",
|
|
9544
|
-
"bundles",
|
|
9545
|
-
"bundle_patches",
|
|
9546
|
-
DYNAMODB_CHANNEL_PARTITION,
|
|
9547
|
-
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9548
|
-
DYNAMODB_INSIGHTS_PARTITION,
|
|
9549
|
-
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9550
|
-
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9551
|
-
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9552
|
-
"_hot-updater#insights-user#*",
|
|
9553
|
-
DYNAMODB_API_KEY_PARTITION,
|
|
9554
|
-
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9555
|
-
] } },
|
|
9556
|
-
Effect: "Allow",
|
|
9557
|
-
Resource: [tableArn]
|
|
9558
|
-
}]
|
|
9559
|
-
}),
|
|
9596
|
+
PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
|
|
9560
9597
|
PolicyName: "HotUpdaterDynamoDBReadAccess",
|
|
9561
9598
|
RoleName: roleName
|
|
9562
9599
|
});
|
|
9563
9600
|
}
|
|
9564
9601
|
async ensureS3Policy(iamClient, roleName, bucketName) {
|
|
9565
9602
|
await iamClient.putRolePolicy({
|
|
9566
|
-
PolicyDocument: JSON.stringify(
|
|
9567
|
-
Version: "2012-10-17",
|
|
9568
|
-
Statement: [{
|
|
9569
|
-
Action: ["s3:ListBucket"],
|
|
9570
|
-
Effect: "Allow",
|
|
9571
|
-
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9572
|
-
}, {
|
|
9573
|
-
Action: ["s3:GetObject"],
|
|
9574
|
-
Effect: "Allow",
|
|
9575
|
-
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9576
|
-
}]
|
|
9577
|
-
}),
|
|
9603
|
+
PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
|
|
9578
9604
|
PolicyName: "HotUpdaterS3ReadAccess",
|
|
9579
9605
|
RoleName: roleName
|
|
9580
9606
|
});
|
|
9581
9607
|
}
|
|
9582
9608
|
async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
|
|
9583
|
-
const parameterPath = parameterName.replace(/^\/+/, "");
|
|
9584
9609
|
await iamClient.putRolePolicy({
|
|
9585
|
-
PolicyDocument: JSON.stringify(
|
|
9586
|
-
Version: "2012-10-17",
|
|
9587
|
-
Statement: [{
|
|
9588
|
-
Effect: "Allow",
|
|
9589
|
-
Action: ["ssm:GetParameter"],
|
|
9590
|
-
Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
|
|
9591
|
-
}]
|
|
9592
|
-
}),
|
|
9610
|
+
PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
|
|
9593
9611
|
PolicyName: "HotUpdaterSSMAccess",
|
|
9594
9612
|
RoleName: roleName
|
|
9595
9613
|
});
|
|
@@ -9604,14 +9622,7 @@ var IAMManager = class {
|
|
|
9604
9622
|
credentials: this.credentials
|
|
9605
9623
|
}).getCallerIdentity({})).Account;
|
|
9606
9624
|
if (!accountId) throw new Error("Failed to get AWS account ID");
|
|
9607
|
-
const assumeRolePolicyDocument = JSON.stringify(
|
|
9608
|
-
Version: "2012-10-17",
|
|
9609
|
-
Statement: [{
|
|
9610
|
-
Effect: "Allow",
|
|
9611
|
-
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9612
|
-
Action: "sts:AssumeRole"
|
|
9613
|
-
}]
|
|
9614
|
-
});
|
|
9625
|
+
const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
|
|
9615
9626
|
const roleName = `hot-updater-edge-${(0, node_crypto.createHash)("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
|
|
9616
9627
|
try {
|
|
9617
9628
|
const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
|
|
@@ -10282,6 +10293,12 @@ const runInit = async ({ build, envFile }) => {
|
|
|
10282
10293
|
_hot_updater_cli_tools.p.log.success("Done! 🎉");
|
|
10283
10294
|
};
|
|
10284
10295
|
//#endregion
|
|
10296
|
+
exports.LAMBDA_EDGE_TRUST_POLICY = LAMBDA_EDGE_TRUST_POLICY;
|
|
10297
|
+
exports.buildDynamoDBBackupInput = buildDynamoDBBackupInput;
|
|
10298
|
+
exports.buildDynamoDBCreateTableInput = buildDynamoDBCreateTableInput;
|
|
10299
|
+
exports.buildDynamoDBPolicy = buildDynamoDBPolicy;
|
|
10300
|
+
exports.buildS3Policy = buildS3Policy;
|
|
10301
|
+
exports.buildSsmPolicy = buildSsmPolicy;
|
|
10285
10302
|
exports.prepareDynamoDBApiKey = prepareDynamoDBApiKey;
|
|
10286
10303
|
exports.prepareDynamoDBDeployment = prepareDynamoDBDeployment;
|
|
10287
10304
|
exports.runInit = runInit;
|
package/dist/iac/index.d.cts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
1
|
import { RunInitOptions } from "@hot-updater/cli-tools";
|
|
2
2
|
import { ApiKeyModel } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region iac/dynamodb.d.ts
|
|
4
|
+
declare const buildDynamoDBCreateTableInput: (tableName: string) => {
|
|
5
|
+
AttributeDefinitions: {
|
|
6
|
+
AttributeName: string;
|
|
7
|
+
AttributeType: "S";
|
|
8
|
+
}[];
|
|
9
|
+
BillingMode: "PAY_PER_REQUEST";
|
|
10
|
+
DeletionProtectionEnabled: true;
|
|
11
|
+
GlobalSecondaryIndexes: {
|
|
12
|
+
IndexName: string;
|
|
13
|
+
KeySchema: ({
|
|
14
|
+
readonly AttributeName: "gsi1pk";
|
|
15
|
+
readonly KeyType: "HASH";
|
|
16
|
+
} | {
|
|
17
|
+
readonly AttributeName: "gsi1sk";
|
|
18
|
+
readonly KeyType: "RANGE";
|
|
19
|
+
})[];
|
|
20
|
+
Projection: {
|
|
21
|
+
ProjectionType: "ALL";
|
|
22
|
+
};
|
|
23
|
+
OnDemandThroughput: {
|
|
24
|
+
readonly MaxReadRequestUnits: 4000;
|
|
25
|
+
readonly MaxWriteRequestUnits: 100;
|
|
26
|
+
};
|
|
27
|
+
}[];
|
|
28
|
+
KeySchema: ({
|
|
29
|
+
readonly AttributeName: "pk";
|
|
30
|
+
readonly KeyType: "HASH";
|
|
31
|
+
} | {
|
|
32
|
+
readonly AttributeName: "sk";
|
|
33
|
+
readonly KeyType: "RANGE";
|
|
34
|
+
})[];
|
|
35
|
+
OnDemandThroughput: {
|
|
36
|
+
readonly MaxReadRequestUnits: 4000;
|
|
37
|
+
readonly MaxWriteRequestUnits: 100;
|
|
38
|
+
};
|
|
39
|
+
TableName: string;
|
|
40
|
+
};
|
|
41
|
+
declare const buildDynamoDBBackupInput: (tableName: string) => {
|
|
42
|
+
PointInTimeRecoverySpecification: {
|
|
43
|
+
PointInTimeRecoveryEnabled: true;
|
|
44
|
+
};
|
|
45
|
+
TableName: string;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region iac/iam.d.ts
|
|
49
|
+
declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
|
|
50
|
+
Version: string;
|
|
51
|
+
Statement: ({
|
|
52
|
+
Action: string[];
|
|
53
|
+
Effect: string;
|
|
54
|
+
Resource: string[];
|
|
55
|
+
Condition?: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
Action: string[];
|
|
58
|
+
Condition: {
|
|
59
|
+
"ForAllValues:StringLike": {
|
|
60
|
+
"dynamodb:LeadingKeys": string[];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
Effect: string;
|
|
64
|
+
Resource: string[];
|
|
65
|
+
})[];
|
|
66
|
+
};
|
|
67
|
+
declare const buildS3Policy: (bucketName: string) => {
|
|
68
|
+
Version: string;
|
|
69
|
+
Statement: {
|
|
70
|
+
Action: string[];
|
|
71
|
+
Effect: string;
|
|
72
|
+
Resource: string[];
|
|
73
|
+
}[];
|
|
74
|
+
};
|
|
75
|
+
declare const buildSsmPolicy: (region: string, accountId: string, parameterName: string) => {
|
|
76
|
+
Version: string;
|
|
77
|
+
Statement: {
|
|
78
|
+
Effect: string;
|
|
79
|
+
Action: string[];
|
|
80
|
+
Resource: string;
|
|
81
|
+
}[];
|
|
82
|
+
};
|
|
83
|
+
declare const LAMBDA_EDGE_TRUST_POLICY: {
|
|
84
|
+
Version: string;
|
|
85
|
+
Statement: {
|
|
86
|
+
Effect: string;
|
|
87
|
+
Principal: {
|
|
88
|
+
Service: string[];
|
|
89
|
+
};
|
|
90
|
+
Action: string;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
3
94
|
//#region iac/index.d.ts
|
|
4
95
|
declare const prepareDynamoDBDeployment: (input: {
|
|
5
96
|
readonly credentials: {
|
|
@@ -16,4 +107,4 @@ declare const prepareDynamoDBApiKey: (input: {
|
|
|
16
107
|
}) => Promise<string>;
|
|
17
108
|
declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
|
|
18
109
|
//#endregion
|
|
19
|
-
export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
|
110
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
package/dist/iac/index.d.mts
CHANGED
|
@@ -1,5 +1,96 @@
|
|
|
1
|
-
import { RunInitOptions } from "@hot-updater/cli-tools";
|
|
1
|
+
import { InitError, RunInitOptions } from "@hot-updater/cli-tools";
|
|
2
2
|
import { ApiKeyModel } from "@hot-updater/plugin-core";
|
|
3
|
+
//#region iac/dynamodb.d.ts
|
|
4
|
+
declare const buildDynamoDBCreateTableInput: (tableName: string) => {
|
|
5
|
+
AttributeDefinitions: {
|
|
6
|
+
AttributeName: string;
|
|
7
|
+
AttributeType: "S";
|
|
8
|
+
}[];
|
|
9
|
+
BillingMode: "PAY_PER_REQUEST";
|
|
10
|
+
DeletionProtectionEnabled: true;
|
|
11
|
+
GlobalSecondaryIndexes: {
|
|
12
|
+
IndexName: string;
|
|
13
|
+
KeySchema: ({
|
|
14
|
+
readonly AttributeName: "gsi1pk";
|
|
15
|
+
readonly KeyType: "HASH";
|
|
16
|
+
} | {
|
|
17
|
+
readonly AttributeName: "gsi1sk";
|
|
18
|
+
readonly KeyType: "RANGE";
|
|
19
|
+
})[];
|
|
20
|
+
Projection: {
|
|
21
|
+
ProjectionType: "ALL";
|
|
22
|
+
};
|
|
23
|
+
OnDemandThroughput: {
|
|
24
|
+
readonly MaxReadRequestUnits: 4000;
|
|
25
|
+
readonly MaxWriteRequestUnits: 100;
|
|
26
|
+
};
|
|
27
|
+
}[];
|
|
28
|
+
KeySchema: ({
|
|
29
|
+
readonly AttributeName: "pk";
|
|
30
|
+
readonly KeyType: "HASH";
|
|
31
|
+
} | {
|
|
32
|
+
readonly AttributeName: "sk";
|
|
33
|
+
readonly KeyType: "RANGE";
|
|
34
|
+
})[];
|
|
35
|
+
OnDemandThroughput: {
|
|
36
|
+
readonly MaxReadRequestUnits: 4000;
|
|
37
|
+
readonly MaxWriteRequestUnits: 100;
|
|
38
|
+
};
|
|
39
|
+
TableName: string;
|
|
40
|
+
};
|
|
41
|
+
declare const buildDynamoDBBackupInput: (tableName: string) => {
|
|
42
|
+
PointInTimeRecoverySpecification: {
|
|
43
|
+
PointInTimeRecoveryEnabled: true;
|
|
44
|
+
};
|
|
45
|
+
TableName: string;
|
|
46
|
+
};
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region iac/iam.d.ts
|
|
49
|
+
declare const buildDynamoDBPolicy: (region: string, accountId: string, tableName: string) => {
|
|
50
|
+
Version: string;
|
|
51
|
+
Statement: ({
|
|
52
|
+
Action: string[];
|
|
53
|
+
Effect: string;
|
|
54
|
+
Resource: string[];
|
|
55
|
+
Condition?: undefined;
|
|
56
|
+
} | {
|
|
57
|
+
Action: string[];
|
|
58
|
+
Condition: {
|
|
59
|
+
"ForAllValues:StringLike": {
|
|
60
|
+
"dynamodb:LeadingKeys": string[];
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
Effect: string;
|
|
64
|
+
Resource: string[];
|
|
65
|
+
})[];
|
|
66
|
+
};
|
|
67
|
+
declare const buildS3Policy: (bucketName: string) => {
|
|
68
|
+
Version: string;
|
|
69
|
+
Statement: {
|
|
70
|
+
Action: string[];
|
|
71
|
+
Effect: string;
|
|
72
|
+
Resource: string[];
|
|
73
|
+
}[];
|
|
74
|
+
};
|
|
75
|
+
declare const buildSsmPolicy: (region: string, accountId: string, parameterName: string) => {
|
|
76
|
+
Version: string;
|
|
77
|
+
Statement: {
|
|
78
|
+
Effect: string;
|
|
79
|
+
Action: string[];
|
|
80
|
+
Resource: string;
|
|
81
|
+
}[];
|
|
82
|
+
};
|
|
83
|
+
declare const LAMBDA_EDGE_TRUST_POLICY: {
|
|
84
|
+
Version: string;
|
|
85
|
+
Statement: {
|
|
86
|
+
Effect: string;
|
|
87
|
+
Principal: {
|
|
88
|
+
Service: string[];
|
|
89
|
+
};
|
|
90
|
+
Action: string;
|
|
91
|
+
}[];
|
|
92
|
+
};
|
|
93
|
+
//#endregion
|
|
3
94
|
//#region iac/index.d.ts
|
|
4
95
|
declare const prepareDynamoDBDeployment: (input: {
|
|
5
96
|
readonly credentials: {
|
|
@@ -16,4 +107,4 @@ declare const prepareDynamoDBApiKey: (input: {
|
|
|
16
107
|
}) => Promise<string>;
|
|
17
108
|
declare const runInit: ({ build, envFile }: RunInitOptions) => Promise<void>;
|
|
18
109
|
//#endregion
|
|
19
|
-
export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
|
110
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
package/dist/iac/index.mjs
CHANGED
|
@@ -7834,10 +7834,10 @@ const hasValidBundleEventShape = (value) => {
|
|
|
7834
7834
|
const type = field(value, "type");
|
|
7835
7835
|
const fromBundleId = field(value, "from_bundle_id");
|
|
7836
7836
|
const updateStrategy = field(value, "update_strategy");
|
|
7837
|
-
return (type === "UPDATE_APPLIED" || type === "RECOVERED"
|
|
7837
|
+
return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
|
|
7838
7838
|
};
|
|
7839
7839
|
const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
|
|
7840
|
-
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "
|
|
7840
|
+
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
|
|
7841
7841
|
const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
|
|
7842
7842
|
const parseOfficialRowItem = (value, partition, isRow) => {
|
|
7843
7843
|
if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
|
|
@@ -7859,7 +7859,7 @@ const insightsBundlePartition = (filter) => {
|
|
|
7859
7859
|
]);
|
|
7860
7860
|
return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${createHash("sha256").update(scope, "utf8").digest("hex")}`;
|
|
7861
7861
|
};
|
|
7862
|
-
const toInsightsBundleItem = (row) =>
|
|
7862
|
+
const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
|
|
7863
7863
|
pk: insightsBundlePartition({
|
|
7864
7864
|
platform: row.platform,
|
|
7865
7865
|
channel: row.channel,
|
|
@@ -7945,10 +7945,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
|
|
|
7945
7945
|
TableName: store.tableName,
|
|
7946
7946
|
Item: eventItem
|
|
7947
7947
|
} },
|
|
7948
|
-
|
|
7948
|
+
{ Put: {
|
|
7949
7949
|
TableName: store.tableName,
|
|
7950
7950
|
Item: bundleItem
|
|
7951
|
-
} }
|
|
7951
|
+
} }
|
|
7952
7952
|
];
|
|
7953
7953
|
if (current === null || advancesInsightsInstallation(next, current.row)) {
|
|
7954
7954
|
actions.push({ Put: {
|
|
@@ -9432,6 +9432,41 @@ const hasExpectedSchema = (table) => {
|
|
|
9432
9432
|
return keyAttributes.every((attributeName) => table?.AttributeDefinitions?.some(({ AttributeName, AttributeType }) => AttributeName === attributeName && AttributeType === "S")) && table?.BillingModeSummary?.BillingMode === "PAY_PER_REQUEST" && table.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && table.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && hasKeySchema(table?.KeySchema, primaryKeySchema) && hasKeySchema(updateIndex?.KeySchema, updateIndexKeySchema) && updateIndex?.OnDemandThroughput?.MaxReadRequestUnits === onDemandThroughput.MaxReadRequestUnits && updateIndex?.OnDemandThroughput?.MaxWriteRequestUnits === onDemandThroughput.MaxWriteRequestUnits && updateIndex?.Projection?.ProjectionType === "ALL";
|
|
9433
9433
|
};
|
|
9434
9434
|
const isResourceNotFound = (error) => typeof error === "object" && error !== null && Reflect.get(error, "name") === "ResourceNotFoundException";
|
|
9435
|
+
const buildDynamoDBCreateTableInput = (tableName) => ({
|
|
9436
|
+
AttributeDefinitions: [
|
|
9437
|
+
{
|
|
9438
|
+
AttributeName: "pk",
|
|
9439
|
+
AttributeType: "S"
|
|
9440
|
+
},
|
|
9441
|
+
{
|
|
9442
|
+
AttributeName: "sk",
|
|
9443
|
+
AttributeType: "S"
|
|
9444
|
+
},
|
|
9445
|
+
{
|
|
9446
|
+
AttributeName: "gsi1pk",
|
|
9447
|
+
AttributeType: "S"
|
|
9448
|
+
},
|
|
9449
|
+
{
|
|
9450
|
+
AttributeName: "gsi1sk",
|
|
9451
|
+
AttributeType: "S"
|
|
9452
|
+
}
|
|
9453
|
+
],
|
|
9454
|
+
BillingMode: "PAY_PER_REQUEST",
|
|
9455
|
+
DeletionProtectionEnabled: true,
|
|
9456
|
+
GlobalSecondaryIndexes: [{
|
|
9457
|
+
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9458
|
+
KeySchema: [...updateIndexKeySchema],
|
|
9459
|
+
Projection: { ProjectionType: "ALL" },
|
|
9460
|
+
OnDemandThroughput: onDemandThroughput
|
|
9461
|
+
}],
|
|
9462
|
+
KeySchema: [...primaryKeySchema],
|
|
9463
|
+
OnDemandThroughput: onDemandThroughput,
|
|
9464
|
+
TableName: tableName
|
|
9465
|
+
});
|
|
9466
|
+
const buildDynamoDBBackupInput = (tableName) => ({
|
|
9467
|
+
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9468
|
+
TableName: tableName
|
|
9469
|
+
});
|
|
9435
9470
|
var DynamoDBManager = class {
|
|
9436
9471
|
region;
|
|
9437
9472
|
client;
|
|
@@ -9452,37 +9487,7 @@ var DynamoDBManager = class {
|
|
|
9452
9487
|
if (error instanceof Error && error.name === "AccessDeniedException" && error.message.includes(DYNAMODB_DESCRIBE_TABLE_ACTION)) throw new DynamoDBPermissionError(tableName, this.region, error);
|
|
9453
9488
|
if (!isResourceNotFound(error)) throw error;
|
|
9454
9489
|
}
|
|
9455
|
-
await this.client.createTable(
|
|
9456
|
-
AttributeDefinitions: [
|
|
9457
|
-
{
|
|
9458
|
-
AttributeName: "pk",
|
|
9459
|
-
AttributeType: "S"
|
|
9460
|
-
},
|
|
9461
|
-
{
|
|
9462
|
-
AttributeName: "sk",
|
|
9463
|
-
AttributeType: "S"
|
|
9464
|
-
},
|
|
9465
|
-
{
|
|
9466
|
-
AttributeName: "gsi1pk",
|
|
9467
|
-
AttributeType: "S"
|
|
9468
|
-
},
|
|
9469
|
-
{
|
|
9470
|
-
AttributeName: "gsi1sk",
|
|
9471
|
-
AttributeType: "S"
|
|
9472
|
-
}
|
|
9473
|
-
],
|
|
9474
|
-
BillingMode: "PAY_PER_REQUEST",
|
|
9475
|
-
DeletionProtectionEnabled: true,
|
|
9476
|
-
GlobalSecondaryIndexes: [{
|
|
9477
|
-
IndexName: DYNAMODB_UPDATE_INDEX_NAME,
|
|
9478
|
-
KeySchema: [...updateIndexKeySchema],
|
|
9479
|
-
Projection: { ProjectionType: "ALL" },
|
|
9480
|
-
OnDemandThroughput: onDemandThroughput
|
|
9481
|
-
}],
|
|
9482
|
-
KeySchema: [...primaryKeySchema],
|
|
9483
|
-
OnDemandThroughput: onDemandThroughput,
|
|
9484
|
-
TableName: tableName
|
|
9485
|
-
});
|
|
9490
|
+
await this.client.createTable(buildDynamoDBCreateTableInput(tableName));
|
|
9486
9491
|
await waitUntilTableExists({
|
|
9487
9492
|
client: this.client,
|
|
9488
9493
|
maxWaitTime: 120
|
|
@@ -9492,14 +9497,80 @@ var DynamoDBManager = class {
|
|
|
9492
9497
|
async ensureLifecycle(tableName) {
|
|
9493
9498
|
const { ContinuousBackupsDescription } = await this.client.describeContinuousBackups({ TableName: tableName });
|
|
9494
9499
|
if (ContinuousBackupsDescription?.PointInTimeRecoveryDescription?.PointInTimeRecoveryStatus === "ENABLED") return;
|
|
9495
|
-
await this.client.updateContinuousBackups(
|
|
9496
|
-
PointInTimeRecoverySpecification: { PointInTimeRecoveryEnabled: true },
|
|
9497
|
-
TableName: tableName
|
|
9498
|
-
});
|
|
9500
|
+
await this.client.updateContinuousBackups(buildDynamoDBBackupInput(tableName));
|
|
9499
9501
|
}
|
|
9500
9502
|
};
|
|
9501
9503
|
//#endregion
|
|
9502
9504
|
//#region iac/iam.ts
|
|
9505
|
+
const buildDynamoDBPolicy = (region, accountId, tableName) => {
|
|
9506
|
+
const tableArn = `arn:aws:dynamodb:${region}:${accountId}:table/${tableName}`;
|
|
9507
|
+
return {
|
|
9508
|
+
Version: "2012-10-17",
|
|
9509
|
+
Statement: [{
|
|
9510
|
+
Action: ["dynamodb:Query"],
|
|
9511
|
+
Effect: "Allow",
|
|
9512
|
+
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9513
|
+
}, {
|
|
9514
|
+
Action: [
|
|
9515
|
+
"dynamodb:BatchGetItem",
|
|
9516
|
+
"dynamodb:DeleteItem",
|
|
9517
|
+
"dynamodb:GetItem",
|
|
9518
|
+
"dynamodb:PutItem",
|
|
9519
|
+
"dynamodb:Query",
|
|
9520
|
+
"dynamodb:TransactWriteItems",
|
|
9521
|
+
"dynamodb:UpdateItem"
|
|
9522
|
+
],
|
|
9523
|
+
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9524
|
+
"_hot-updater",
|
|
9525
|
+
"bundles",
|
|
9526
|
+
"bundle_patches",
|
|
9527
|
+
DYNAMODB_CHANNEL_PARTITION,
|
|
9528
|
+
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9529
|
+
DYNAMODB_INSIGHTS_PARTITION,
|
|
9530
|
+
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9531
|
+
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9532
|
+
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9533
|
+
"_hot-updater#insights-user#*",
|
|
9534
|
+
DYNAMODB_API_KEY_PARTITION,
|
|
9535
|
+
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9536
|
+
] } },
|
|
9537
|
+
Effect: "Allow",
|
|
9538
|
+
Resource: [tableArn]
|
|
9539
|
+
}]
|
|
9540
|
+
};
|
|
9541
|
+
};
|
|
9542
|
+
const buildS3Policy = (bucketName) => {
|
|
9543
|
+
return {
|
|
9544
|
+
Version: "2012-10-17",
|
|
9545
|
+
Statement: [{
|
|
9546
|
+
Action: ["s3:ListBucket"],
|
|
9547
|
+
Effect: "Allow",
|
|
9548
|
+
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9549
|
+
}, {
|
|
9550
|
+
Action: ["s3:GetObject"],
|
|
9551
|
+
Effect: "Allow",
|
|
9552
|
+
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9553
|
+
}]
|
|
9554
|
+
};
|
|
9555
|
+
};
|
|
9556
|
+
const buildSsmPolicy = (region, accountId, parameterName) => {
|
|
9557
|
+
return {
|
|
9558
|
+
Version: "2012-10-17",
|
|
9559
|
+
Statement: [{
|
|
9560
|
+
Effect: "Allow",
|
|
9561
|
+
Action: ["ssm:GetParameter"],
|
|
9562
|
+
Resource: `arn:aws:ssm:${region}:${accountId}:parameter/${parameterName.replace(/^\/+/, "")}`
|
|
9563
|
+
}]
|
|
9564
|
+
};
|
|
9565
|
+
};
|
|
9566
|
+
const LAMBDA_EDGE_TRUST_POLICY = {
|
|
9567
|
+
Version: "2012-10-17",
|
|
9568
|
+
Statement: [{
|
|
9569
|
+
Effect: "Allow",
|
|
9570
|
+
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9571
|
+
Action: "sts:AssumeRole"
|
|
9572
|
+
}]
|
|
9573
|
+
};
|
|
9503
9574
|
var IAMManager = class {
|
|
9504
9575
|
region;
|
|
9505
9576
|
credentials;
|
|
@@ -9516,75 +9587,22 @@ var IAMManager = class {
|
|
|
9516
9587
|
});
|
|
9517
9588
|
}
|
|
9518
9589
|
async ensureDynamoDBPolicy(iamClient, roleName, accountId, tableName) {
|
|
9519
|
-
const tableArn = `arn:aws:dynamodb:${this.region}:${accountId}:table/${tableName}`;
|
|
9520
9590
|
await iamClient.putRolePolicy({
|
|
9521
|
-
PolicyDocument: JSON.stringify(
|
|
9522
|
-
Version: "2012-10-17",
|
|
9523
|
-
Statement: [{
|
|
9524
|
-
Action: ["dynamodb:Query"],
|
|
9525
|
-
Effect: "Allow",
|
|
9526
|
-
Resource: [`${tableArn}/index/${DYNAMODB_UPDATE_INDEX_NAME}`]
|
|
9527
|
-
}, {
|
|
9528
|
-
Action: [
|
|
9529
|
-
"dynamodb:BatchGetItem",
|
|
9530
|
-
"dynamodb:DeleteItem",
|
|
9531
|
-
"dynamodb:GetItem",
|
|
9532
|
-
"dynamodb:PutItem",
|
|
9533
|
-
"dynamodb:Query",
|
|
9534
|
-
"dynamodb:TransactWriteItems",
|
|
9535
|
-
"dynamodb:UpdateItem"
|
|
9536
|
-
],
|
|
9537
|
-
Condition: { "ForAllValues:StringLike": { "dynamodb:LeadingKeys": [
|
|
9538
|
-
"_hot-updater",
|
|
9539
|
-
"bundles",
|
|
9540
|
-
"bundle_patches",
|
|
9541
|
-
DYNAMODB_CHANNEL_PARTITION,
|
|
9542
|
-
DYNAMODB_CHANNEL_NAME_PARTITION,
|
|
9543
|
-
DYNAMODB_INSIGHTS_PARTITION,
|
|
9544
|
-
DYNAMODB_INSIGHTS_INSTALLATIONS_PARTITION,
|
|
9545
|
-
DYNAMODB_INSIGHTS_EVENT_IDS_PARTITION,
|
|
9546
|
-
`${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}*`,
|
|
9547
|
-
"_hot-updater#insights-user#*",
|
|
9548
|
-
DYNAMODB_API_KEY_PARTITION,
|
|
9549
|
-
DYNAMODB_API_KEY_HASH_PARTITION
|
|
9550
|
-
] } },
|
|
9551
|
-
Effect: "Allow",
|
|
9552
|
-
Resource: [tableArn]
|
|
9553
|
-
}]
|
|
9554
|
-
}),
|
|
9591
|
+
PolicyDocument: JSON.stringify(buildDynamoDBPolicy(this.region, accountId, tableName)),
|
|
9555
9592
|
PolicyName: "HotUpdaterDynamoDBReadAccess",
|
|
9556
9593
|
RoleName: roleName
|
|
9557
9594
|
});
|
|
9558
9595
|
}
|
|
9559
9596
|
async ensureS3Policy(iamClient, roleName, bucketName) {
|
|
9560
9597
|
await iamClient.putRolePolicy({
|
|
9561
|
-
PolicyDocument: JSON.stringify(
|
|
9562
|
-
Version: "2012-10-17",
|
|
9563
|
-
Statement: [{
|
|
9564
|
-
Action: ["s3:ListBucket"],
|
|
9565
|
-
Effect: "Allow",
|
|
9566
|
-
Resource: [`arn:aws:s3:::${bucketName}`]
|
|
9567
|
-
}, {
|
|
9568
|
-
Action: ["s3:GetObject"],
|
|
9569
|
-
Effect: "Allow",
|
|
9570
|
-
Resource: [`arn:aws:s3:::${bucketName}/*`]
|
|
9571
|
-
}]
|
|
9572
|
-
}),
|
|
9598
|
+
PolicyDocument: JSON.stringify(buildS3Policy(bucketName)),
|
|
9573
9599
|
PolicyName: "HotUpdaterS3ReadAccess",
|
|
9574
9600
|
RoleName: roleName
|
|
9575
9601
|
});
|
|
9576
9602
|
}
|
|
9577
9603
|
async ensureSsmPolicy(iamClient, roleName, accountId, parameterName) {
|
|
9578
|
-
const parameterPath = parameterName.replace(/^\/+/, "");
|
|
9579
9604
|
await iamClient.putRolePolicy({
|
|
9580
|
-
PolicyDocument: JSON.stringify(
|
|
9581
|
-
Version: "2012-10-17",
|
|
9582
|
-
Statement: [{
|
|
9583
|
-
Effect: "Allow",
|
|
9584
|
-
Action: ["ssm:GetParameter"],
|
|
9585
|
-
Resource: `arn:aws:ssm:${this.region}:${accountId}:parameter/${parameterPath}`
|
|
9586
|
-
}]
|
|
9587
|
-
}),
|
|
9605
|
+
PolicyDocument: JSON.stringify(buildSsmPolicy(this.region, accountId, parameterName)),
|
|
9588
9606
|
PolicyName: "HotUpdaterSSMAccess",
|
|
9589
9607
|
RoleName: roleName
|
|
9590
9608
|
});
|
|
@@ -9599,14 +9617,7 @@ var IAMManager = class {
|
|
|
9599
9617
|
credentials: this.credentials
|
|
9600
9618
|
}).getCallerIdentity({})).Account;
|
|
9601
9619
|
if (!accountId) throw new Error("Failed to get AWS account ID");
|
|
9602
|
-
const assumeRolePolicyDocument = JSON.stringify(
|
|
9603
|
-
Version: "2012-10-17",
|
|
9604
|
-
Statement: [{
|
|
9605
|
-
Effect: "Allow",
|
|
9606
|
-
Principal: { Service: ["lambda.amazonaws.com", "edgelambda.amazonaws.com"] },
|
|
9607
|
-
Action: "sts:AssumeRole"
|
|
9608
|
-
}]
|
|
9609
|
-
});
|
|
9620
|
+
const assumeRolePolicyDocument = JSON.stringify(LAMBDA_EDGE_TRUST_POLICY);
|
|
9610
9621
|
const roleName = `hot-updater-edge-${createHash("sha256").update(options.lambdaName).digest("hex").slice(0, 16)}`;
|
|
9611
9622
|
try {
|
|
9612
9623
|
const { Role: existingRole } = await iamClient.getRole({ RoleName: roleName });
|
|
@@ -10277,4 +10288,4 @@ const runInit = async ({ build, envFile }) => {
|
|
|
10277
10288
|
p.log.success("Done! 🎉");
|
|
10278
10289
|
};
|
|
10279
10290
|
//#endregion
|
|
10280
|
-
export { prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
|
10291
|
+
export { LAMBDA_EDGE_TRUST_POLICY, buildDynamoDBBackupInput, buildDynamoDBCreateTableInput, buildDynamoDBPolicy, buildS3Policy, buildSsmPolicy, prepareDynamoDBApiKey, prepareDynamoDBDeployment, runInit };
|
package/dist/index.cjs
CHANGED
|
@@ -1575,10 +1575,10 @@ const hasValidBundleEventShape = (value) => {
|
|
|
1575
1575
|
const type = field(value, "type");
|
|
1576
1576
|
const fromBundleId = field(value, "from_bundle_id");
|
|
1577
1577
|
const updateStrategy = field(value, "update_strategy");
|
|
1578
|
-
return (type === "UPDATE_APPLIED" || type === "RECOVERED"
|
|
1578
|
+
return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
|
|
1579
1579
|
};
|
|
1580
1580
|
const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
|
|
1581
|
-
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "
|
|
1581
|
+
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
|
|
1582
1582
|
const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
|
|
1583
1583
|
const parseOfficialRowItem = (value, partition, isRow) => {
|
|
1584
1584
|
if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
|
|
@@ -1600,7 +1600,7 @@ const insightsBundlePartition = (filter) => {
|
|
|
1600
1600
|
]);
|
|
1601
1601
|
return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${(0, node_crypto.createHash)("sha256").update(scope, "utf8").digest("hex")}`;
|
|
1602
1602
|
};
|
|
1603
|
-
const toInsightsBundleItem = (row) =>
|
|
1603
|
+
const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
|
|
1604
1604
|
pk: insightsBundlePartition({
|
|
1605
1605
|
platform: row.platform,
|
|
1606
1606
|
channel: row.channel,
|
|
@@ -1686,10 +1686,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
|
|
|
1686
1686
|
TableName: store.tableName,
|
|
1687
1687
|
Item: eventItem
|
|
1688
1688
|
} },
|
|
1689
|
-
|
|
1689
|
+
{ Put: {
|
|
1690
1690
|
TableName: store.tableName,
|
|
1691
1691
|
Item: bundleItem
|
|
1692
|
-
} }
|
|
1692
|
+
} }
|
|
1693
1693
|
];
|
|
1694
1694
|
if (current === null || advancesInsightsInstallation(next, current.row)) {
|
|
1695
1695
|
actions.push({ Put: {
|
package/dist/index.mjs
CHANGED
|
@@ -1574,10 +1574,10 @@ const hasValidBundleEventShape = (value) => {
|
|
|
1574
1574
|
const type = field(value, "type");
|
|
1575
1575
|
const fromBundleId = field(value, "from_bundle_id");
|
|
1576
1576
|
const updateStrategy = field(value, "update_strategy");
|
|
1577
|
-
return (type === "UPDATE_APPLIED" || type === "RECOVERED"
|
|
1577
|
+
return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
|
|
1578
1578
|
};
|
|
1579
1579
|
const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
|
|
1580
|
-
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "
|
|
1580
|
+
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
|
|
1581
1581
|
const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
|
|
1582
1582
|
const parseOfficialRowItem = (value, partition, isRow) => {
|
|
1583
1583
|
if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
|
|
@@ -1599,7 +1599,7 @@ const insightsBundlePartition = (filter) => {
|
|
|
1599
1599
|
]);
|
|
1600
1600
|
return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${createHash("sha256").update(scope, "utf8").digest("hex")}`;
|
|
1601
1601
|
};
|
|
1602
|
-
const toInsightsBundleItem = (row) =>
|
|
1602
|
+
const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
|
|
1603
1603
|
pk: insightsBundlePartition({
|
|
1604
1604
|
platform: row.platform,
|
|
1605
1605
|
channel: row.channel,
|
|
@@ -1685,10 +1685,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
|
|
|
1685
1685
|
TableName: store.tableName,
|
|
1686
1686
|
Item: eventItem
|
|
1687
1687
|
} },
|
|
1688
|
-
|
|
1688
|
+
{ Put: {
|
|
1689
1689
|
TableName: store.tableName,
|
|
1690
1690
|
Item: bundleItem
|
|
1691
|
-
} }
|
|
1691
|
+
} }
|
|
1692
1692
|
];
|
|
1693
1693
|
if (current === null || advancesInsightsInstallation(next, current.row)) {
|
|
1694
1694
|
actions.push({ Put: {
|
package/dist/lambda/index.cjs
CHANGED
|
@@ -32,7 +32,7 @@ let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
|
|
|
32
32
|
let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
|
|
33
33
|
//#endregion
|
|
34
34
|
//#region ../../packages/server/dist/version.mjs
|
|
35
|
-
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.
|
|
35
|
+
const HOT_UPDATER_SERVER_VERSION = "1.0.0-rc.3";
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
38
38
|
const createVersionRouteHandlers = () => ({ version: async () => Response.json({
|
|
@@ -835,7 +835,7 @@ const modelValidators = {
|
|
|
835
835
|
},
|
|
836
836
|
bundle_events: {
|
|
837
837
|
id: (value) => typeof value === "string",
|
|
838
|
-
type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "
|
|
838
|
+
type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "UNCHANGED",
|
|
839
839
|
install_id: isInsightsIdentityText,
|
|
840
840
|
user_id: isNullableInsightsIdentityText,
|
|
841
841
|
username: (value) => value === null || typeof value === "string",
|
|
@@ -858,7 +858,7 @@ const modelValidators = {
|
|
|
858
858
|
user_id: isNullableInsightsIdentityText,
|
|
859
859
|
username: (value) => value === null || typeof value === "string",
|
|
860
860
|
to_bundle_id: (value) => typeof value === "string",
|
|
861
|
-
type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "
|
|
861
|
+
type: (value) => value === "UPDATE_APPLIED" || value === "RECOVERED" || value === "UNCHANGED",
|
|
862
862
|
platform: (value) => value === "ios" || value === "android",
|
|
863
863
|
app_version: (value) => typeof value === "string",
|
|
864
864
|
channel: (value) => typeof value === "string",
|
|
@@ -1061,7 +1061,7 @@ const hasValidReleaseInvariants = (data) => {
|
|
|
1061
1061
|
const fingerprintHash = data.fingerprint_hash;
|
|
1062
1062
|
return (kind === "BUNDLE" && typeof bundleId === "string" || kind === "EMBEDDED" && bundleId === null) && (strategy === "APP_VERSION" && typeof targetAppVersion === "string" && fingerprintHash === null || strategy === "FINGERPRINT" && targetAppVersion === null && typeof fingerprintHash === "string");
|
|
1063
1063
|
};
|
|
1064
|
-
const hasValidBundleEventInvariants = (data) => (data.type === "UPDATE_APPLIED" || data.type === "RECOVERED"
|
|
1064
|
+
const hasValidBundleEventInvariants = (data) => (data.type === "UPDATE_APPLIED" || data.type === "RECOVERED") && typeof data.from_bundle_id === "string" && (data.update_strategy === "fingerprint" || data.update_strategy === "appVersion") || data.type === "UNCHANGED" && data.from_bundle_id === null && data.update_strategy === null;
|
|
1065
1065
|
const validateCreateData = (model, data) => {
|
|
1066
1066
|
if (!isRecord$3(data)) throw new DatabasePluginInputError("invalid-data");
|
|
1067
1067
|
validateFields(model, Object.keys(data));
|
|
@@ -1131,7 +1131,7 @@ const isBundleFilter = (value, withKind = false) => {
|
|
|
1131
1131
|
"type",
|
|
1132
1132
|
...withKind ? ["kind"] : []
|
|
1133
1133
|
];
|
|
1134
|
-
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys$1(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_APPLIED" || value.type === "
|
|
1134
|
+
return value.type === "RECOVERED" ? isText(value.fromBundleId) && hasOnlyKeys$1(value, [...keys, "fromBundleId"]) : (value.type === "UPDATE_APPLIED" || value.type === "UNCHANGED") && isText(value.toBundleId) && hasOnlyKeys$1(value, [...keys, "toBundleId"]);
|
|
1135
1135
|
};
|
|
1136
1136
|
const isEventFilter = (value) => {
|
|
1137
1137
|
if (!isRecord$3(value)) return false;
|
|
@@ -1165,7 +1165,7 @@ const toInsightsInstallationRow = (event) => {
|
|
|
1165
1165
|
received_at_ms: event.received_at_ms
|
|
1166
1166
|
};
|
|
1167
1167
|
};
|
|
1168
|
-
/**
|
|
1168
|
+
/** Unchanged reports are not bundle movements. */
|
|
1169
1169
|
const isInsightsMovementEvent = (event) => event.type === "UPDATE_APPLIED" || event.type === "RECOVERED";
|
|
1170
1170
|
const matchesInsightsEventFilter = (event, filter) => {
|
|
1171
1171
|
if (filter.kind === "all") return true;
|
|
@@ -4219,8 +4219,7 @@ function requireEvent(payload) {
|
|
|
4219
4219
|
const type = requireStringField(payload, "type");
|
|
4220
4220
|
switch (type) {
|
|
4221
4221
|
case "UPDATE_APPLIED":
|
|
4222
|
-
case "RECOVERED":
|
|
4223
|
-
case "RELEASE_ADOPTED": {
|
|
4222
|
+
case "RECOVERED": {
|
|
4224
4223
|
const updateStrategy = requireStringField(payload, "updateStrategy");
|
|
4225
4224
|
if (updateStrategy !== "fingerprint" && updateStrategy !== "appVersion") throw new InsightsBadRequestError("Invalid event field: updateStrategy");
|
|
4226
4225
|
return {
|
|
@@ -4264,8 +4263,7 @@ function createBundleEventRow(input) {
|
|
|
4264
4263
|
};
|
|
4265
4264
|
switch (input.type) {
|
|
4266
4265
|
case "UPDATE_APPLIED":
|
|
4267
|
-
case "RECOVERED":
|
|
4268
|
-
case "RELEASE_ADOPTED": return {
|
|
4266
|
+
case "RECOVERED": return {
|
|
4269
4267
|
...base,
|
|
4270
4268
|
from_bundle_id: input.fromBundleId,
|
|
4271
4269
|
type: input.type,
|
|
@@ -4332,7 +4330,7 @@ const parseEventPageInput = (request) => {
|
|
|
4332
4330
|
let bundle;
|
|
4333
4331
|
if (bundleFields.some((key) => url.searchParams.has(key))) {
|
|
4334
4332
|
const outcome = readSingle(url, "outcome");
|
|
4335
|
-
if (outcome !== "applied" && outcome !== "recovered" && outcome !== "
|
|
4333
|
+
if (outcome !== "applied" && outcome !== "recovered" && outcome !== "unchanged") throw new InsightsBadRequestError("Invalid 'outcome' query parameter.");
|
|
4336
4334
|
bundle = {
|
|
4337
4335
|
...readScope$1(url),
|
|
4338
4336
|
bundleId: readId(url, "bundleId", 1024),
|
|
@@ -4653,9 +4651,9 @@ const bundleFilter = (input) => {
|
|
|
4653
4651
|
type: "RECOVERED",
|
|
4654
4652
|
fromBundleId: bundleId
|
|
4655
4653
|
};
|
|
4656
|
-
case "
|
|
4654
|
+
case "unchanged": return {
|
|
4657
4655
|
...scope,
|
|
4658
|
-
type: "
|
|
4656
|
+
type: "UNCHANGED",
|
|
4659
4657
|
toBundleId: bundleId
|
|
4660
4658
|
};
|
|
4661
4659
|
default: throw new InsightsBadRequestError("Invalid Insights outcome.");
|
|
@@ -4862,7 +4860,7 @@ const createInsightsProvider = (model) => Object.freeze({
|
|
|
4862
4860
|
sinceMs,
|
|
4863
4861
|
beforeReceivedAtMs
|
|
4864
4862
|
}));
|
|
4865
|
-
const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports,
|
|
4863
|
+
const [reportingInstallations, bundleInstallations, appliedReports, recoveredReports, unchangedReports] = await Promise.all([
|
|
4866
4864
|
reporting,
|
|
4867
4865
|
measure(model.countInstallations({
|
|
4868
4866
|
...scope,
|
|
@@ -4871,7 +4869,7 @@ const createInsightsProvider = (model) => Object.freeze({
|
|
|
4871
4869
|
})),
|
|
4872
4870
|
countOutcome("applied"),
|
|
4873
4871
|
countOutcome("recovered"),
|
|
4874
|
-
countOutcome("
|
|
4872
|
+
countOutcome("unchanged")
|
|
4875
4873
|
]);
|
|
4876
4874
|
return {
|
|
4877
4875
|
...scope,
|
|
@@ -4884,7 +4882,7 @@ const createInsightsProvider = (model) => Object.freeze({
|
|
|
4884
4882
|
reportingInstallations: bundleInstallations,
|
|
4885
4883
|
appliedReports,
|
|
4886
4884
|
recoveredReports,
|
|
4887
|
-
|
|
4885
|
+
unchangedReports
|
|
4888
4886
|
}
|
|
4889
4887
|
};
|
|
4890
4888
|
}
|
|
@@ -9120,10 +9118,10 @@ const hasValidBundleEventShape = (value) => {
|
|
|
9120
9118
|
const type = field(value, "type");
|
|
9121
9119
|
const fromBundleId = field(value, "from_bundle_id");
|
|
9122
9120
|
const updateStrategy = field(value, "update_strategy");
|
|
9123
|
-
return (type === "UPDATE_APPLIED" || type === "RECOVERED"
|
|
9121
|
+
return (type === "UPDATE_APPLIED" || type === "RECOVERED") && typeof fromBundleId === "string" && (updateStrategy === "fingerprint" || updateStrategy === "appVersion") || type === "UNCHANGED" && fromBundleId === null && updateStrategy === null;
|
|
9124
9122
|
};
|
|
9125
9123
|
const isBundleEventRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "from_release_id")) && isNullableString(field(value, "to_release_id")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "received_at_ms") === "number" && hasValidBundleEventShape(value);
|
|
9126
|
-
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "
|
|
9124
|
+
const isInsightsInstallationRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "install_id") === "string" && isNullableString(field(value, "user_id")) && isNullableString(field(value, "username")) && typeof field(value, "to_bundle_id") === "string" && (field(value, "type") === "UPDATE_APPLIED" || field(value, "type") === "RECOVERED" || field(value, "type") === "UNCHANGED") && (field(value, "platform") === "ios" || field(value, "platform") === "android") && typeof field(value, "app_version") === "string" && typeof field(value, "channel") === "string" && typeof field(value, "cohort") === "string" && typeof field(value, "received_at_ms") === "number";
|
|
9127
9125
|
const isApiKeyRow = (value) => typeof value === "object" && value !== null && typeof field(value, "id") === "string" && typeof field(value, "hash") === "string" && typeof field(value, "name") === "string" && typeof field(value, "prefix") === "string" && field(value, "role") === "client" && typeof field(value, "created_at_ms") === "number" && (field(value, "revoked_at_ms") === null || typeof field(value, "revoked_at_ms") === "number");
|
|
9128
9126
|
const parseOfficialRowItem = (value, partition, isRow) => {
|
|
9129
9127
|
if (value.pk !== partition || typeof value.sk !== "string" || typeof value.version !== "number" || !isRow(value.row)) throw new DynamoDBStoredItemError();
|
|
@@ -9145,7 +9143,7 @@ const insightsBundlePartition = (filter) => {
|
|
|
9145
9143
|
]);
|
|
9146
9144
|
return `${DYNAMODB_INSIGHTS_BUNDLE_PREFIX}${(0, node_crypto.createHash)("sha256").update(scope, "utf8").digest("hex")}`;
|
|
9147
9145
|
};
|
|
9148
|
-
const toInsightsBundleItem = (row) =>
|
|
9146
|
+
const toInsightsBundleItem = (row) => boundedDynamoDBMetadataItem({
|
|
9149
9147
|
pk: insightsBundlePartition({
|
|
9150
9148
|
platform: row.platform,
|
|
9151
9149
|
channel: row.channel,
|
|
@@ -9231,10 +9229,10 @@ const recordDynamoDBInsightsEvent = async (store, { event: row, installation: ne
|
|
|
9231
9229
|
TableName: store.tableName,
|
|
9232
9230
|
Item: eventItem
|
|
9233
9231
|
} },
|
|
9234
|
-
|
|
9232
|
+
{ Put: {
|
|
9235
9233
|
TableName: store.tableName,
|
|
9236
9234
|
Item: bundleItem
|
|
9237
|
-
} }
|
|
9235
|
+
} }
|
|
9238
9236
|
];
|
|
9239
9237
|
if (current === null || advancesInsightsInstallation(next, current.row)) {
|
|
9240
9238
|
actions.push({ Put: {
|
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.4",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -51,8 +51,8 @@
|
|
|
51
51
|
"execa": "9.5.2",
|
|
52
52
|
"mime": "^4.0.4",
|
|
53
53
|
"@hot-updater/core": "1.0.0-rc.0",
|
|
54
|
-
"@hot-updater/mock": "1.0.0-rc.
|
|
55
|
-
"@hot-updater/test-utils": "1.0.0-rc.
|
|
54
|
+
"@hot-updater/mock": "1.0.0-rc.3",
|
|
55
|
+
"@hot-updater/test-utils": "1.0.0-rc.3"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@aws-sdk/client-cloudfront": "3.1066.0",
|
|
@@ -68,9 +68,9 @@
|
|
|
68
68
|
"@aws-sdk/lib-dynamodb": "3.1066.0",
|
|
69
69
|
"hono": "4.12.34",
|
|
70
70
|
"aws-lambda": "1.0.7",
|
|
71
|
-
"@hot-updater/cli-tools": "1.0.0-rc.
|
|
72
|
-
"@hot-updater/
|
|
73
|
-
"@hot-updater/
|
|
71
|
+
"@hot-updater/cli-tools": "1.0.0-rc.3",
|
|
72
|
+
"@hot-updater/plugin-core": "1.0.0-rc.3",
|
|
73
|
+
"@hot-updater/server": "1.0.0-rc.3"
|
|
74
74
|
},
|
|
75
75
|
"scripts": {
|
|
76
76
|
"build": "tsdown",
|