diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 9375379..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: [jsynowiec] -patreon: # Replace with a single Patreon username -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/__tests__/poller.test.ts b/__tests__/poller.test.ts index 50d98e6..6badd15 100644 --- a/__tests__/poller.test.ts +++ b/__tests__/poller.test.ts @@ -25,7 +25,6 @@ describe('Poller', () => { let poller: Poller | undefined; afterAll(() => { - console.log('Stopping poller'); poller.stop(); }); diff --git a/examples/infinitePoller.ts b/examples/infinitePoller.ts new file mode 100644 index 0000000..213ee55 --- /dev/null +++ b/examples/infinitePoller.ts @@ -0,0 +1,75 @@ +import { Poller } from '../src/poller.js'; +import { AppConfigDataClient } from '@aws-sdk/client-appconfigdata'; +import { parse } from 'yaml'; +import { fromTemporaryCredentials } from '@aws-sdk/credential-providers'; +import { STSClient, GetCallerIdentityCommand } from '@aws-sdk/client-sts'; +import { AwsCredentialIdentity, Provider } from '@smithy/types'; + +/** + * This is a made-up format, corresponding to an AppConfig profile + * that I created in my personal AWS account for testing. + */ +interface SampleFormat { + Content: { + description: string; + advantages: string[]; + }; +} + +/** + * This helps me avoid mentioning my AWS account id in a public repo. + * https://stackoverflow.com/a/74546015 + * + * Normally there's no need for this. + */ +const getAWSAccountId = async (): Promise => { + const response = await new STSClient().send(new GetCallerIdentityCommand({})); + return String(response.Account); +}; + +/** + * I'm using a temporary credentials provider (based on STS assumeRole) + * to give confidence that the credentials will refresh themselves after + * the duration expires. + */ +const getCredentialsProvider = ( + awsAccountId: string, +): Provider => { + return fromTemporaryCredentials({ + params: { + // This is a role I created in my personal AWS account for testing. + RoleArn: `arn:aws:iam::${awsAccountId}:role/AppConfigReader`, + DurationSeconds: 900, + }, + }); +}; + +const dataClient = new AppConfigDataClient({ + credentials: getCredentialsProvider(await getAWSAccountId()), +}); + +const poller = new Poller({ + dataClient: dataClient, + sessionConfig: { + // These refer to an AppConfig profile I created in my personal + // AWS account for testing. + ApplicationIdentifier: 'PollerTest', + EnvironmentIdentifier: 'Live', + ConfigurationProfileIdentifier: 'YamlTest', + }, + configTransformer: (s): SampleFormat => parse(s), + logger: console.log, + pollIntervalSeconds: 60, +}); + +await poller.start(); + +console.log('Starting at:', new Date()); + +setInterval(() => { + const obj = poller.getConfigurationObject(); + console.log('Current config entry', obj); +}, 1000 * 60); + +// This will run forever until you manually terminate it. +// Normally you would call poller.stop() if you want the program to exit. diff --git a/package-lock.json b/package-lock.json index 78e3729..859787a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "tslib": "~2.6" }, "devDependencies": { + "@aws-sdk/credential-providers": "^3.470.0", "@types/jest": "~29.5", "@types/node": "~18", "@typescript-eslint/eslint-plugin": "~6.2", @@ -26,7 +27,8 @@ "rimraf": "~5.0", "ts-api-utils": "~1.0", "ts-jest": "~29.1", - "typescript": "~5.1" + "typescript": "~5.1", + "yaml": "^2.3.4" }, "engines": { "node": ">= 18.12 <19" @@ -195,6 +197,56 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-cognito-identity": { + "version": "3.470.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.470.0.tgz", + "integrity": "sha512-oE665xfl/KwvbcNtvUxMCKwh+X3wOV5UgPrPSptK+DzUJbtL4FAP7h6QIVzUB5CkzqhQVRAmYvdf+XhfXz3T3g==", + "dev": true, + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.470.0", + "@aws-sdk/core": "3.468.0", + "@aws-sdk/credential-provider-node": "3.470.0", + "@aws-sdk/middleware-host-header": "3.468.0", + "@aws-sdk/middleware-logger": "3.468.0", + "@aws-sdk/middleware-recursion-detection": "3.468.0", + "@aws-sdk/middleware-signing": "3.468.0", + "@aws-sdk/middleware-user-agent": "3.470.0", + "@aws-sdk/region-config-resolver": "3.470.0", + "@aws-sdk/types": "3.468.0", + "@aws-sdk/util-endpoints": "3.470.0", + "@aws-sdk/util-user-agent-browser": "3.468.0", + "@aws-sdk/util-user-agent-node": "3.470.0", + "@smithy/config-resolver": "^2.0.21", + "@smithy/fetch-http-handler": "^2.3.1", + "@smithy/hash-node": "^2.0.17", + "@smithy/invalid-dependency": "^2.0.15", + "@smithy/middleware-content-length": "^2.0.17", + "@smithy/middleware-endpoint": "^2.2.3", + "@smithy/middleware-retry": "^2.0.24", + "@smithy/middleware-serde": "^2.0.15", + "@smithy/middleware-stack": "^2.0.9", + "@smithy/node-config-provider": "^2.1.8", + "@smithy/node-http-handler": "^2.2.1", + "@smithy/protocol-http": "^3.0.11", + "@smithy/smithy-client": "^2.1.18", + "@smithy/types": "^2.7.0", + "@smithy/url-parser": "^2.0.15", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.1", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.22", + "@smithy/util-defaults-mode-node": "^2.0.29", + "@smithy/util-endpoints": "^1.0.7", + "@smithy/util-retry": "^2.0.8", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/client-sso": { "version": "3.470.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.470.0.tgz", @@ -303,6 +355,22 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/credential-provider-cognito-identity": { + "version": "3.470.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.470.0.tgz", + "integrity": "sha512-c0YtiBbg4z/4iLnn3gtUtGKBZMQLRk79LjzCN6x98MpIsRTeEBL+4BHYNwFb8C+S4wVDYh2OWqjw1Su6Ues3Wg==", + "dev": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.470.0", + "@aws-sdk/types": "3.468.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.7.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.468.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.468.0.tgz", @@ -317,6 +385,26 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.468.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.468.0.tgz", + "integrity": "sha512-pUF+gmeCr4F1De69qEsWgnNeF7xzlLcjiGcbpO6u9k6NQdRR7Xr3wTQnQt1+3MgoIdbgoXpCfQYNZ4LfX6B/sA==", + "dev": true, + "dependencies": { + "@aws-sdk/types": "3.468.0", + "@smithy/fetch-http-handler": "^2.3.1", + "@smithy/node-http-handler": "^2.2.1", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.11", + "@smithy/smithy-client": "^2.1.18", + "@smithy/types": "^2.7.0", + "@smithy/util-stream": "^2.0.23", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.470.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.470.0.tgz", @@ -404,6 +492,33 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/credential-providers": { + "version": "3.470.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.470.0.tgz", + "integrity": "sha512-aCI/z6L+LwPSUHTsf27WMs3Z7Dfg1idgEOtf0dCkk+T1SZnJA0D/JS0KjQag9rIuqYQsxewx6RCIHus5WJ3czA==", + "dev": true, + "dependencies": { + "@aws-sdk/client-cognito-identity": "3.470.0", + "@aws-sdk/client-sso": "3.470.0", + "@aws-sdk/client-sts": "3.470.0", + "@aws-sdk/credential-provider-cognito-identity": "3.470.0", + "@aws-sdk/credential-provider-env": "3.468.0", + "@aws-sdk/credential-provider-http": "3.468.0", + "@aws-sdk/credential-provider-ini": "3.470.0", + "@aws-sdk/credential-provider-node": "3.470.0", + "@aws-sdk/credential-provider-process": "3.468.0", + "@aws-sdk/credential-provider-sso": "3.470.0", + "@aws-sdk/credential-provider-web-identity": "3.468.0", + "@aws-sdk/types": "3.468.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.7.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.468.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.468.0.tgz", @@ -6613,6 +6728,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "node_modules/yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -6820,6 +6944,53 @@ "tslib": "^2.5.0" } }, + "@aws-sdk/client-cognito-identity": { + "version": "3.470.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.470.0.tgz", + "integrity": "sha512-oE665xfl/KwvbcNtvUxMCKwh+X3wOV5UgPrPSptK+DzUJbtL4FAP7h6QIVzUB5CkzqhQVRAmYvdf+XhfXz3T3g==", + "dev": true, + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.470.0", + "@aws-sdk/core": "3.468.0", + "@aws-sdk/credential-provider-node": "3.470.0", + "@aws-sdk/middleware-host-header": "3.468.0", + "@aws-sdk/middleware-logger": "3.468.0", + "@aws-sdk/middleware-recursion-detection": "3.468.0", + "@aws-sdk/middleware-signing": "3.468.0", + "@aws-sdk/middleware-user-agent": "3.470.0", + "@aws-sdk/region-config-resolver": "3.470.0", + "@aws-sdk/types": "3.468.0", + "@aws-sdk/util-endpoints": "3.470.0", + "@aws-sdk/util-user-agent-browser": "3.468.0", + "@aws-sdk/util-user-agent-node": "3.470.0", + "@smithy/config-resolver": "^2.0.21", + "@smithy/fetch-http-handler": "^2.3.1", + "@smithy/hash-node": "^2.0.17", + "@smithy/invalid-dependency": "^2.0.15", + "@smithy/middleware-content-length": "^2.0.17", + "@smithy/middleware-endpoint": "^2.2.3", + "@smithy/middleware-retry": "^2.0.24", + "@smithy/middleware-serde": "^2.0.15", + "@smithy/middleware-stack": "^2.0.9", + "@smithy/node-config-provider": "^2.1.8", + "@smithy/node-http-handler": "^2.2.1", + "@smithy/protocol-http": "^3.0.11", + "@smithy/smithy-client": "^2.1.18", + "@smithy/types": "^2.7.0", + "@smithy/url-parser": "^2.0.15", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.1", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.22", + "@smithy/util-defaults-mode-node": "^2.0.29", + "@smithy/util-endpoints": "^1.0.7", + "@smithy/util-retry": "^2.0.8", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, "@aws-sdk/client-sso": { "version": "3.470.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.470.0.tgz", @@ -6919,6 +7090,19 @@ "tslib": "^2.5.0" } }, + "@aws-sdk/credential-provider-cognito-identity": { + "version": "3.470.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.470.0.tgz", + "integrity": "sha512-c0YtiBbg4z/4iLnn3gtUtGKBZMQLRk79LjzCN6x98MpIsRTeEBL+4BHYNwFb8C+S4wVDYh2OWqjw1Su6Ues3Wg==", + "dev": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.470.0", + "@aws-sdk/types": "3.468.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.7.0", + "tslib": "^2.5.0" + } + }, "@aws-sdk/credential-provider-env": { "version": "3.468.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.468.0.tgz", @@ -6930,6 +7114,23 @@ "tslib": "^2.5.0" } }, + "@aws-sdk/credential-provider-http": { + "version": "3.468.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.468.0.tgz", + "integrity": "sha512-pUF+gmeCr4F1De69qEsWgnNeF7xzlLcjiGcbpO6u9k6NQdRR7Xr3wTQnQt1+3MgoIdbgoXpCfQYNZ4LfX6B/sA==", + "dev": true, + "requires": { + "@aws-sdk/types": "3.468.0", + "@smithy/fetch-http-handler": "^2.3.1", + "@smithy/node-http-handler": "^2.2.1", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.11", + "@smithy/smithy-client": "^2.1.18", + "@smithy/types": "^2.7.0", + "@smithy/util-stream": "^2.0.23", + "tslib": "^2.5.0" + } + }, "@aws-sdk/credential-provider-ini": { "version": "3.470.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.470.0.tgz", @@ -7002,6 +7203,30 @@ "tslib": "^2.5.0" } }, + "@aws-sdk/credential-providers": { + "version": "3.470.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.470.0.tgz", + "integrity": "sha512-aCI/z6L+LwPSUHTsf27WMs3Z7Dfg1idgEOtf0dCkk+T1SZnJA0D/JS0KjQag9rIuqYQsxewx6RCIHus5WJ3czA==", + "dev": true, + "requires": { + "@aws-sdk/client-cognito-identity": "3.470.0", + "@aws-sdk/client-sso": "3.470.0", + "@aws-sdk/client-sts": "3.470.0", + "@aws-sdk/credential-provider-cognito-identity": "3.470.0", + "@aws-sdk/credential-provider-env": "3.468.0", + "@aws-sdk/credential-provider-http": "3.468.0", + "@aws-sdk/credential-provider-ini": "3.470.0", + "@aws-sdk/credential-provider-node": "3.470.0", + "@aws-sdk/credential-provider-process": "3.468.0", + "@aws-sdk/credential-provider-sso": "3.470.0", + "@aws-sdk/credential-provider-web-identity": "3.468.0", + "@aws-sdk/types": "3.468.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.7.0", + "tslib": "^2.5.0" + } + }, "@aws-sdk/middleware-host-header": { "version": "3.468.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.468.0.tgz", @@ -11576,6 +11801,12 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "yaml": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", + "dev": true + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index 3a9e706..3957981 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "node": ">= 18.12 <19" }, "devDependencies": { + "@aws-sdk/credential-providers": "^3.470.0", "@types/jest": "~29.5", "@types/node": "~18", "@typescript-eslint/eslint-plugin": "~6.2", @@ -20,10 +21,10 @@ "rimraf": "~5.0", "ts-api-utils": "~1.0", "ts-jest": "~29.1", - "typescript": "~5.1" + "typescript": "~5.1", + "yaml": "^2.3.4" }, "scripts": { - "start": "node build/src/main.js", "clean": "rimraf coverage build tmp", "prebuild": "npm run lint", "build": "tsc -p tsconfig.json", @@ -32,7 +33,8 @@ "lint": "eslint . --ext .ts --ext .mts", "test": "jest --coverage", "prettier": "prettier --config .prettierrc --write .", - "test:watch": "jest --watch" + "test:watch": "jest --watch", + "infinite": "tsc && node build/examples/infinitePoller.js" }, "author": "Tyler Arehart ", "license": "Apache-2.0", diff --git a/src/poller.ts b/src/poller.ts index f09a854..f77056a 100644 --- a/src/poller.ts +++ b/src/poller.ts @@ -149,10 +149,11 @@ export class Poller { this.configurationToken = getResponse.NextPollConfigurationToken; } - if (getResponse.Configuration) { + const stringValue = getResponse.Configuration?.transformToString(); + + if (stringValue) { try { - this.configStringStore.latestValue = - getResponse.Configuration.transformToString(); + this.configStringStore.latestValue = stringValue; this.configStringStore.lastFreshTime = new Date(); this.configStringStore.versionLabel = getResponse.VersionLabel; this.configStringStore.errorCausingStaleValue = undefined; @@ -176,8 +177,9 @@ export class Poller { logger?.('Config string and object have gone stale', e); } } else { - // When the configuration in the getResponse is not defined, that means the configuration is + // When the configuration in the getResponse is empty, that means the configuration is // unchanged from the last time we polled. + // https://docs.aws.amazon.com/appconfig/2019-10-09/APIReference/API_appconfigdata_GetLatestConfiguration.html this.configStringStore.lastFreshTime = new Date(); this.configObjectStore.lastFreshTime = new Date(); } diff --git a/tsconfig.json b/tsconfig.json index 4a94f7f..debd3bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,5 +19,5 @@ "noImplicitThis": false, "strictNullChecks": false }, - "include": ["src/**/*", "__tests__/**/*"] + "include": ["src/**/*", "__tests__/**/*", "examples/**/*"] }