From 1da98e7a55330311b99d606b27ec9cd9dff1ddba Mon Sep 17 00:00:00 2001 From: Di Wu Date: Thu, 2 Nov 2023 15:00:51 -0700 Subject: [PATCH 1/2] fix(analytics): add validation for flushInterval bottom --- .../utils/resolveConfig.test.ts | 14 +++++++++++++ .../kinesis/utils/resolveConfig.test.ts | 11 ++++++++++ .../personalize/utils/resolveConfig.test.ts | 14 +++++++++++++ packages/analytics/src/errors/validation.ts | 20 +++++++++++-------- .../kinesis-firehose/utils/resolveConfig.ts | 7 +++++++ .../providers/kinesis/utils/resolveConfig.ts | 7 +++++++ .../personalize/utils/resolveConfig.ts | 6 ++++++ packages/analytics/src/utils/constants.ts | 4 ++++ 8 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 packages/analytics/src/utils/constants.ts diff --git a/packages/analytics/__tests__/providers/kinesis-firehose/utils/resolveConfig.test.ts b/packages/analytics/__tests__/providers/kinesis-firehose/utils/resolveConfig.test.ts index 62ceaba295e..c366602cd4d 100644 --- a/packages/analytics/__tests__/providers/kinesis-firehose/utils/resolveConfig.test.ts +++ b/packages/analytics/__tests__/providers/kinesis-firehose/utils/resolveConfig.test.ts @@ -4,6 +4,7 @@ import { Amplify } from '@aws-amplify/core'; import { resolveConfig } from '../../../../src/providers/kinesis-firehose/utils'; import { DEFAULT_KINESIS_FIREHOSE_CONFIG } from '../../../../src/providers/kinesis-firehose/utils/constants'; +import { FLUSH_INTERVAL_MIN } from '../../../../src/utils/constants'; describe('Analytics KinesisFirehose Provider Util: resolveConfig', () => { const providedConfig = { @@ -65,4 +66,17 @@ describe('Analytics KinesisFirehose Provider Util: resolveConfig', () => { expect(resolveConfig).toThrow(); }); + + it('throws if flushInterval is smaller than min', () => { + getConfigSpy.mockReturnValue({ + Analytics: { + KinesisFirehose: { + ...providedConfig, + flushInterval: FLUSH_INTERVAL_MIN - 1, + }, + }, + }); + + expect(resolveConfig).toThrow(); + }); }); diff --git a/packages/analytics/__tests__/providers/kinesis/utils/resolveConfig.test.ts b/packages/analytics/__tests__/providers/kinesis/utils/resolveConfig.test.ts index a0b39266770..5bd49186afe 100644 --- a/packages/analytics/__tests__/providers/kinesis/utils/resolveConfig.test.ts +++ b/packages/analytics/__tests__/providers/kinesis/utils/resolveConfig.test.ts @@ -4,6 +4,7 @@ import { Amplify } from '@aws-amplify/core'; import { resolveConfig } from '../../../../src/providers/kinesis/utils/resolveConfig'; import { DEFAULT_KINESIS_CONFIG } from '../../../../src/providers/kinesis/utils/constants'; +import { FLUSH_INTERVAL_MIN } from '../../../../src/utils/constants'; describe('Analytics Kinesis Provider Util: resolveConfig', () => { const kinesisConfig = { @@ -62,4 +63,14 @@ describe('Analytics Kinesis Provider Util: resolveConfig', () => { expect(resolveConfig).toThrow(); }); + + it('throws if flushInterval is smaller than min', () => { + getConfigSpy.mockReturnValue({ + Analytics: { + Kinesis: { ...kinesisConfig, flushInterval: FLUSH_INTERVAL_MIN - 1 }, + }, + }); + + expect(resolveConfig).toThrow(); + }); }); diff --git a/packages/analytics/__tests__/providers/personalize/utils/resolveConfig.test.ts b/packages/analytics/__tests__/providers/personalize/utils/resolveConfig.test.ts index 093bc6079a6..d820dc6c846 100644 --- a/packages/analytics/__tests__/providers/personalize/utils/resolveConfig.test.ts +++ b/packages/analytics/__tests__/providers/personalize/utils/resolveConfig.test.ts @@ -7,6 +7,7 @@ import { DEFAULT_PERSONALIZE_CONFIG, PERSONALIZE_FLUSH_SIZE_MAX, } from '../../../../src/providers/personalize/utils'; +import { FLUSH_INTERVAL_MIN } from '../../../../src/utils/constants'; describe('Analytics Personalize Provider Util: resolveConfig', () => { const providedConfig = { @@ -70,4 +71,17 @@ describe('Analytics Personalize Provider Util: resolveConfig', () => { expect(resolveConfig).toThrow(); }); + + it('throws if flushInterval is smaller than min', () => { + getConfigSpy.mockReturnValue({ + Analytics: { + Personalize: { + ...providedConfig, + flushInterval: FLUSH_INTERVAL_MIN - 1, + }, + }, + }); + + expect(resolveConfig).toThrow(); + }); }); diff --git a/packages/analytics/src/errors/validation.ts b/packages/analytics/src/errors/validation.ts index 5ccafeabc44..ef0080be382 100644 --- a/packages/analytics/src/errors/validation.ts +++ b/packages/analytics/src/errors/validation.ts @@ -8,10 +8,11 @@ export enum AnalyticsValidationErrorCode { NoCredentials = 'NoCredentials', NoEventName = 'NoEventName', NoRegion = 'NoRegion', - InvalidTracker = 'InvalidTracker', - UnsupportedPlatform = 'UnsupportedPlatform', NoTrackingId = 'NoTrackingId', + InvalidFlushInterval = 'InvalidFlushInterval', InvalidFlushSize = 'InvalidFlushSize', + InvalidTracker = 'InvalidTracker', + UnsupportedPlatform = 'UnsupportedPlatform', } export const validationErrorMap: AmplifyErrorMap = @@ -28,16 +29,19 @@ export const validationErrorMap: AmplifyErrorMap = [AnalyticsValidationErrorCode.NoRegion]: { message: 'Missing region.', }, + [AnalyticsValidationErrorCode.NoTrackingId]: { + message: 'A trackingId is required to use Amazon Personalize.', + }, + [AnalyticsValidationErrorCode.InvalidFlushInterval]: { + message: 'Invalid FlushInterval.', + }, + [AnalyticsValidationErrorCode.InvalidFlushSize]: { + message: 'Invalid FlushSize, it should be smaller than BufferSize.', + }, [AnalyticsValidationErrorCode.InvalidTracker]: { message: 'Invalid tracker type specified.', }, [AnalyticsValidationErrorCode.UnsupportedPlatform]: { message: 'Only session tracking is supported on React Native.', }, - [AnalyticsValidationErrorCode.InvalidFlushSize]: { - message: 'Invalid FlushSize, it should be smaller than BufferSize', - }, - [AnalyticsValidationErrorCode.NoTrackingId]: { - message: 'A trackingId is required to use Amazon Personalize', - }, }; diff --git a/packages/analytics/src/providers/kinesis-firehose/utils/resolveConfig.ts b/packages/analytics/src/providers/kinesis-firehose/utils/resolveConfig.ts index 7ce9969a259..85b52e32d5a 100644 --- a/packages/analytics/src/providers/kinesis-firehose/utils/resolveConfig.ts +++ b/packages/analytics/src/providers/kinesis-firehose/utils/resolveConfig.ts @@ -7,6 +7,7 @@ import { assertValidationError, } from '../../../errors'; import { DEFAULT_KINESIS_FIREHOSE_CONFIG } from './constants'; +import { FLUSH_INTERVAL_MIN } from '../../../utils/constants'; export const resolveConfig = () => { const config = Amplify.getConfig().Analytics?.KinesisFirehose; @@ -26,6 +27,12 @@ export const resolveConfig = () => { flushSize < bufferSize, AnalyticsValidationErrorCode.InvalidFlushSize ); + assertValidationError( + flushInterval >= FLUSH_INTERVAL_MIN, + AnalyticsValidationErrorCode.InvalidFlushInterval, + `FlushInterval should be greater than or equal to ${FLUSH_INTERVAL_MIN} milliseconds` + ); + return { region, bufferSize, diff --git a/packages/analytics/src/providers/kinesis/utils/resolveConfig.ts b/packages/analytics/src/providers/kinesis/utils/resolveConfig.ts index 2eaf8edf4ff..35422f4dfa9 100644 --- a/packages/analytics/src/providers/kinesis/utils/resolveConfig.ts +++ b/packages/analytics/src/providers/kinesis/utils/resolveConfig.ts @@ -7,6 +7,7 @@ import { assertValidationError, } from '../../../errors'; import { DEFAULT_KINESIS_CONFIG } from './constants'; +import { FLUSH_INTERVAL_MIN } from '../../../utils/constants'; export const resolveConfig = () => { const config = Amplify.getConfig().Analytics?.Kinesis; @@ -26,6 +27,12 @@ export const resolveConfig = () => { flushSize < bufferSize, AnalyticsValidationErrorCode.InvalidFlushSize ); + assertValidationError( + flushInterval >= FLUSH_INTERVAL_MIN, + AnalyticsValidationErrorCode.InvalidFlushInterval, + `FlushInterval should be greater than or equal to ${FLUSH_INTERVAL_MIN} milliseconds` + ); + return { region, bufferSize, diff --git a/packages/analytics/src/providers/personalize/utils/resolveConfig.ts b/packages/analytics/src/providers/personalize/utils/resolveConfig.ts index 0c4d9641a98..74bcdcf4e82 100644 --- a/packages/analytics/src/providers/personalize/utils/resolveConfig.ts +++ b/packages/analytics/src/providers/personalize/utils/resolveConfig.ts @@ -7,6 +7,7 @@ import { assertValidationError, } from '../../../errors'; import { DEFAULT_PERSONALIZE_CONFIG, PERSONALIZE_FLUSH_SIZE_MAX } from './'; +import { FLUSH_INTERVAL_MIN } from '../../../utils/constants'; export const resolveConfig = () => { const config = Amplify.getConfig().Analytics?.Personalize; @@ -30,6 +31,11 @@ export const resolveConfig = () => { AnalyticsValidationErrorCode.InvalidFlushSize, `FlushSize for Personalize should be less or equal than ${PERSONALIZE_FLUSH_SIZE_MAX}` ); + assertValidationError( + flushInterval >= FLUSH_INTERVAL_MIN, + AnalyticsValidationErrorCode.InvalidFlushInterval, + `FlushInterval should be greater than or equal to ${FLUSH_INTERVAL_MIN} milliseconds` + ); return { region, diff --git a/packages/analytics/src/utils/constants.ts b/packages/analytics/src/utils/constants.ts new file mode 100644 index 00000000000..6a80a9d5baf --- /dev/null +++ b/packages/analytics/src/utils/constants.ts @@ -0,0 +1,4 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +export const FLUSH_INTERVAL_MIN = 100; From 917ea4850e5200a6f4907e27d2a87f5497494f0e Mon Sep 17 00:00:00 2001 From: Di Wu Date: Thu, 2 Nov 2023 16:24:24 -0700 Subject: [PATCH 2/2] fix(analytics): periodically submit events with recursive setTimeout --- .../analytics/src/utils/eventBuffer/EventBuffer.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/analytics/src/utils/eventBuffer/EventBuffer.ts b/packages/analytics/src/utils/eventBuffer/EventBuffer.ts index c71ffd15855..8af55c9a05e 100644 --- a/packages/analytics/src/utils/eventBuffer/EventBuffer.ts +++ b/packages/analytics/src/utils/eventBuffer/EventBuffer.ts @@ -11,7 +11,7 @@ export class EventBuffer { private readonly config: EventBufferConfig; private getAnalyticsClient: () => IAnalyticsClient; - private timer?: ReturnType; + private timer?: ReturnType; constructor( config: EventBufferConfig, @@ -60,13 +60,16 @@ export class EventBuffer { private startEventLoop() { if (this.timer) { - clearInterval(this.timer); + clearTimeout(this.timer); } const { flushSize, flushInterval } = this.config; - setInterval(() => { - this.submitEvents(flushSize); - }, flushInterval); + const submit = () => { + this.timer = setTimeout(() => { + this.submitEvents(flushSize).finally(() => submit()); + }, flushInterval); + }; + submit(); } private submitEvents(count: number): Promise {