@aws-amplify/core
Version:
Core category of aws-amplify
187 lines (184 loc) • 6.7 kB
JavaScript
import { ConsoleLogger } from '../../../Logger/ConsoleLogger.mjs';
import '../../../utils/getClientInfo/getClientInfo.mjs';
import '../../../utils/retry/retry.mjs';
import '../../../types/errors.mjs';
import '@aws-crypto/sha256-js';
import '@smithy/util-hex-encoding';
import '../../../errors/errorHelpers.mjs';
import '../../../awsClients/pinpoint/base.mjs';
import { putEvents } from '../../../awsClients/pinpoint/putEvents.mjs';
import { isAppInForeground } from './isAppInForeground.mjs';
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
const logger = new ConsoleLogger('PinpointEventBuffer');
const RETRYABLE_CODES = [429, 500];
const ACCEPTED_CODES = [202];
class PinpointEventBuffer {
constructor(config) {
this._interval = undefined;
this._pause = false;
this._flush = false;
this._buffer = [];
this._config = config;
this._sendBatch = this._sendBatch.bind(this);
this._startLoop();
}
push(event) {
if (this._buffer.length >= this._config.bufferSize) {
logger.debug('Exceeded Pinpoint event buffer limits, event dropped.', {
eventId: event.eventId,
});
return;
}
this._buffer.push({ [event.eventId]: event });
}
pause() {
this._pause = true;
}
resume() {
this._pause = false;
}
flush() {
this._flush = true;
}
identityHasChanged(identityId) {
return this._config.identityId !== identityId;
}
flushAll() {
this._putEvents(this._buffer.splice(0, this._buffer.length));
}
_startLoop() {
if (this._interval) {
clearInterval(this._interval);
}
const { flushInterval } = this._config;
this._interval = setInterval(this._sendBatch, flushInterval);
}
_sendBatch() {
const bufferLength = this._buffer.length;
if (this._flush && !bufferLength && this._interval) {
clearInterval(this._interval);
}
if (this._pause || !bufferLength || !isAppInForeground()) {
return;
}
const { flushSize } = this._config;
const batchSize = Math.min(flushSize, bufferLength);
const bufferSubset = this._buffer.splice(0, batchSize);
this._putEvents(bufferSubset);
}
async _putEvents(buffer) {
const eventMap = this._bufferToMap(buffer);
const batchEventParams = this._generateBatchEventParams(eventMap);
try {
const { credentials, region, userAgentValue } = this._config;
const data = await putEvents({
credentials,
region,
userAgentValue,
}, batchEventParams);
this._processPutEventsSuccessResponse(data, eventMap);
}
catch (err) {
this._handlePutEventsFailure(err, eventMap);
}
}
_generateBatchEventParams(eventMap) {
const batchItem = {};
Object.values(eventMap).forEach(item => {
const { event, timestamp, endpointId, eventId, session } = item;
const { name, attributes, metrics } = event;
batchItem[endpointId] = {
Endpoint: {
...batchItem[endpointId]?.Endpoint,
},
Events: {
...batchItem[endpointId]?.Events,
[eventId]: {
EventType: name,
Timestamp: new Date(timestamp).toISOString(),
Attributes: attributes,
Metrics: metrics,
Session: session,
},
},
};
});
return {
ApplicationId: this._config.appId,
EventsRequest: {
BatchItem: batchItem,
},
};
}
_handlePutEventsFailure(err, eventMap) {
logger.debug('putEvents call to Pinpoint failed.', err);
const statusCode = err.$metadata && err.$metadata.httpStatusCode;
if (RETRYABLE_CODES.includes(statusCode)) {
const retryableEvents = Object.values(eventMap);
this._retry(retryableEvents);
}
}
_processPutEventsSuccessResponse(data, eventMap) {
const { Results = {} } = data.EventsResponse ?? {};
const retryableEvents = [];
Object.entries(Results).forEach(([_, endpointValues]) => {
const responses = endpointValues.EventsItemResponse ?? {};
Object.entries(responses).forEach(([eventId, eventValues]) => {
const eventObject = eventMap[eventId];
if (!eventObject) {
return;
}
const { StatusCode, Message } = eventValues ?? {};
if (StatusCode && ACCEPTED_CODES.includes(StatusCode)) {
return;
}
if (StatusCode && RETRYABLE_CODES.includes(StatusCode)) {
retryableEvents.push(eventObject);
return;
}
const { name } = eventObject.event;
logger.warn('Pinpoint event failed to send.', {
eventId,
name,
message: Message,
});
});
});
if (retryableEvents.length) {
this._retry(retryableEvents);
}
}
_retry(retryableEvents) {
// retryable events that haven't reached the resendLimit
const eligibleEvents = [];
retryableEvents.forEach((bufferedEvent) => {
const { eventId } = bufferedEvent;
const { name } = bufferedEvent.event;
if (bufferedEvent.resendLimit-- > 0) {
logger.debug('Resending event.', {
eventId,
name,
remainingAttempts: bufferedEvent.resendLimit,
});
eligibleEvents.push({ [eventId]: bufferedEvent });
return;
}
logger.debug('No retry attempts remaining for event.', {
eventId,
name,
});
});
// add the events to the front of the buffer
this._buffer.unshift(...eligibleEvents);
}
_bufferToMap(buffer) {
return buffer.reduce((acc, curVal) => {
const [[key, value]] = Object.entries(curVal);
acc[key] = value;
return acc;
}, {});
}
}
export { PinpointEventBuffer };
//# sourceMappingURL=PinpointEventBuffer.mjs.map