Skip to content

Commit

Permalink
refactor: Updated pinpoint provider (#5)
Browse files Browse the repository at this point in the history
refactor: Updated pinpoint provider

* Removed race condition on initialization

* Added private API for updating endpoint

* Updated to use pinpoint client more directly without need to add client abstraction layer

* Added shared endpoint util

* Refactored and renamed cached endpoint util

* Add types to cachedUuid functions

* Add types to cachedUuid function parameters

Co-authored-by: Chris Fang <chrfang@amazon.com>
  • Loading branch information
cshfang and Chris Fang authored Jul 27, 2021
1 parent 16c8583 commit 0103f0a
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 157 deletions.
20 changes: 2 additions & 18 deletions packages/analytics/src/Providers/AWSPinpointProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
PutEventsCommandInput,
UpdateEndpointCommand,
} from '@aws-sdk/client-pinpoint';
import Cache from '@aws-amplify/cache';
import { getCachedUuid as getEndpointId } from '@aws-amplify/cache';

import {
AnalyticsProvider,
Expand Down Expand Up @@ -126,7 +126,7 @@ export class AWSPinpointProvider implements AnalyticsProvider {
if (this._config.appId && !this._config.disabled) {
if (!this._config.endpointId) {
const cacheKey = this.getProviderName() + '_' + this._config.appId;
this._getEndpointId(cacheKey)
getEndpointId(cacheKey)
.then(endpointId => {
logger.debug('setting endpoint id from the cache', endpointId);
this._config.endpointId = endpointId;
Expand Down Expand Up @@ -603,22 +603,6 @@ export class AWSPinpointProvider implements AnalyticsProvider {
// }
}

private async _getEndpointId(cacheKey) {
// try to get from cache
let endpointId = await Cache.getItem(cacheKey);
logger.debug(
'endpointId from cache',
endpointId,
'type',
typeof endpointId
);
if (!endpointId) {
endpointId = uuid();
Cache.setItem(cacheKey, endpointId);
}
return endpointId;
}

/**
* EndPoint request
* @return {Object} - The request of updating endpoint
Expand Down
5 changes: 5 additions & 0 deletions packages/cache/.watchmanconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"ignore_dirs": [
"node_modules"
]
}
37 changes: 37 additions & 0 deletions packages/cache/src/Utils/CachedUuid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import Cache from '../AsyncStorageCache';
import { v1 as uuid } from 'uuid';

const uuids = {};
const promises = {};

export const getCachedUuid = (cacheKey: string): Promise<string> => {
// if uuid for a key has been created, just resolve
if (uuids[cacheKey]) {
return Promise.resolve(uuids[cacheKey]);
}
// if uuid for a key has not been created, ensure only one creation process is running
if (!promises[cacheKey]) {
promises[cacheKey] = getUuid(cacheKey);
}
return promises[cacheKey];
};

const getUuid = (cacheKey: string): Promise<string> =>
new Promise(async (resolve, reject) => {
try {
const cachedUuid = await Cache.getItem(cacheKey);
if (cachedUuid) {
uuids[cacheKey] = cachedUuid;
resolve(uuids[cacheKey]);
return;
}
const generatedUuid = uuid();
await Cache.setItem(cacheKey, generatedUuid);
uuids[cacheKey] = generatedUuid;
resolve(uuids[cacheKey]);
} catch (err) {
reject(err);
} finally {
delete promises[cacheKey];
}
});
1 change: 1 addition & 0 deletions packages/cache/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { Amplify } from '@aws-amplify/core';
import { BrowserStorageCache } from './BrowserStorageCache';
import { InMemoryCache } from './InMemoryCache';
import { CacheConfig } from './types';
export { getCachedUuid } from './Utils/CachedUuid';

export { BrowserStorageCache, InMemoryCache, CacheConfig };
/**
Expand Down
1 change: 1 addition & 0 deletions packages/cache/src/reactnative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import { Amplify } from '@aws-amplify/core';
import { Cache, AsyncStorageCache } from './AsyncStorageCache';
export { getCachedUuid } from './Utils/CachedUuid';

export { Cache, AsyncStorageCache };
/**
Expand Down
2 changes: 1 addition & 1 deletion packages/notifications/src/Notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import {
const STORAGE_KEY_SUFFIX = '_notificationKey';
const noop = () => {};

const logger = new Logger('NotificationsClass');
const logger = new Logger('Notifications');

class NotificationsClass {
private config: Record<string, any> = {};
Expand Down
157 changes: 83 additions & 74 deletions packages/notifications/src/Providers/AWSPinpointProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,38 @@
*/

import { ConsoleLogger as Logger, Credentials, Hub } from '@aws-amplify/core';
import Cache from '@aws-amplify/cache';
import { getCachedUuid as getEndpointId } from '@aws-amplify/cache';
import { UpdateEndpointRequest } from '@aws-sdk/client-pinpoint';
import { v1 as uuid } from 'uuid';

import { getInAppMessages } from './client';
import TempPinpointClient from './client';
import { NotificationsCategory, NotificationsProvider } from '../types';

const AMPLIFY_SYMBOL = (typeof Symbol !== 'undefined' &&
typeof Symbol.for === 'function'
? Symbol.for('amplify_default')
: '@@amplify_default') as Symbol;

const dispatchNotificationEvent = (event: string, data: any) => {
const dispatchNotificationEvent = (
event: string,
data: any,
message?: string
) => {
Hub.dispatch('notification', { event, data }, 'Notification', AMPLIFY_SYMBOL);
};

const logger = new Logger('AWSPinpointProvider');

// params: { event: {name: , .... }, timeStamp, config, resendLimits }
export default class AWSPinpointProvider implements NotificationsProvider {
static category: NotificationsCategory = 'Notifications';
static providerName = 'AWSPinpoint';

private config;
private endpointUpdated = false;
private pinpointClient;

constructor(config?) {
this.config = config ? config : {};
constructor(config = {}) {
this.config = config;
}

/**
Expand All @@ -54,89 +60,92 @@ export default class AWSPinpointProvider implements NotificationsProvider {
return AWSPinpointProvider.providerName;
}

configure = (config): object => {
logger.debug('configure Analytics', config);
const conf = config || {};
this.config = Object.assign({}, this.config, conf);

if (this.config.appId && !this.config.disabled) {
if (!this.config.endpointId) {
const cacheKey = this.getProviderName() + '_' + this.config.appId;
this._getEndpointId(cacheKey)
.then(endpointId => {
logger.debug('setting endpoint id from the cache', endpointId);
this.config.endpointId = endpointId;
dispatchNotificationEvent('pinpointProvider_configured', null);
})
.catch(err => {
logger.debug('Failed to generate endpointId', err);
});
} else {
dispatchNotificationEvent('pinpointProvider_configured', null);
configure = (config = {}): object => {
logger.debug('configure', config);
this.config = Object.assign({}, this.config, config);
dispatchNotificationEvent('pinpointProvider_configured', null);
return this.config;
};

syncInAppMessages = async () => {
const { appId, disabled, endpointId } = this.config;

if (disabled) {
logger.debug('provider is disabled');
return;
}

if (!endpointId) {
const cacheKey = `${this.getProviderName()}_${appId}`;
this.config.endpointId = await getEndpointId(cacheKey);
}

try {
await this.initClient();
if (!this.endpointUpdated) {
await this.updateEndpoint();
}
const response = await this.pinpointClient
.getInAppMessages({
ApplicationId: appId,
EndpointId: endpointId || this.config.endpointId,
})
.promise();
const { InAppMessageCampaigns } = response.InAppMessagesResponse;
dispatchNotificationEvent('syncInAppMessages', InAppMessageCampaigns);
return InAppMessageCampaigns;
} catch (err) {
logger.error('Error syncing in-app messages', err);
}
return this.config;
};

private async _getCredentials() {
private initClient = async () => {
if (this.pinpointClient) {
return;
}

const { appId, region } = this.config;
const credentials = await this.getCredentials();

if (!appId || !credentials || !region) {
throw new Error(
'One or more of credentials, appId or region is not configured'
);
}

this.pinpointClient = new TempPinpointClient({ region, ...credentials });
};

private getCredentials = async () => {
try {
const credentials = await Credentials.get();
if (!credentials) {
logger.debug('no credentials found');
return null;
}

logger.debug('set credentials for in app messages', credentials);
return Credentials.shear(credentials);
} catch (err) {
logger.debug('ensure credentials error', err);
logger.error('Error getting credentials', err);
return null;
}
}

private async _getEndpointId(cacheKey: string) {
// try to get from cache or generate
let endpointId = await Cache.getItem(cacheKey);
logger.debug(
'endpointId from cache',
endpointId,
'type',
typeof endpointId
);
if (!endpointId) {
endpointId = uuid();
}
return endpointId;
}
};

async syncInAppMessages() {
private updateEndpoint = async (): Promise<void> => {
const { appId, endpointId } = this.config;
const request: UpdateEndpointRequest = {
ApplicationId: appId,
EndpointId: endpointId,
EndpointRequest: {
RequestId: uuid(),
EffectiveDate: new Date().toISOString(),
},
};
try {
const { appId, region } = this.config;

const cacheKey = `${this.getProviderName()}_${appId}`;
const endpointId = await this._getEndpointId(cacheKey);
const credentials = await this._getCredentials();

if (!credentials || !appId || !region) {
logger.debug(
'cannot sync inAppMessages without credentials, applicationId and region'
);
return Promise.reject(
new Error('No credentials, applicationId or region')
);
}

const messages = await getInAppMessages({
appId,
credentials,
endpointId,
region,
});

return messages;
} catch (e) {
// TODO: Add error handling
console.warn(e);
logger.debug('updating endpoint', request);
await this.pinpointClient.updateEndpoint(request).promise();
this.endpointUpdated = true;
} catch (err) {
throw err;
}
}
};
}
59 changes: 17 additions & 42 deletions packages/notifications/src/Providers/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,22 @@
import { InAppNotificationsResponse } from '../../types';
require('aws-sdk/lib/node_loader');

import PinpointMessagesClient from './pinpointClient';
const AWS = require('aws-sdk/lib/core');
const Service = AWS.Service;
const apiLoader = AWS.apiLoader;

// Gamma URL
// const url = 'https://714kasriok.execute-api.us-east-1.amazonaws.com';
// Prod
const url = 'https://pinpoint.us-east-1.amazonaws.com/';
const PINPOINT = 'pinpoint';

export function getInAppMessages({ appId, credentials, endpointId, region }) {
return getInAppCampaigns({
appId,
credentials,
endpointId,
region,
});
}
apiLoader.services[PINPOINT] = {};
AWS.Pinpoint = Service.defineService('pinpoint', ['2016-12-01']);
Object.defineProperty(apiLoader.services[PINPOINT], '2016-12-01', {
get: function get() {
const model = require('./pinpoint-2016-12-01.min.json');
return model;
},
enumerable: true,
configurable: true,
});

export async function getInAppCampaigns({
appId,
credentials,
endpointId,
region,
}) {
const options = {
endpoint: url,
region,
...credentials,
};
const pinpointInternal = new PinpointMessagesClient(options);
const PinpointClient = AWS.Pinpoint;

return pinpointInternal
.getInAppMessages(getInAppMessagesRequest(appId, endpointId))
.promise()
.then(
(response: InAppNotificationsResponse) =>
response.InAppMessagesResponse.InAppMessageCampaigns
)
.catch(err => console.warn('Error: ' + err));
}

function getInAppMessagesRequest(appId: string, endpointId: string) {
return {
ApplicationId: appId,
EndpointId: endpointId,
};
}
export default PinpointClient;
Loading

0 comments on commit 0103f0a

Please sign in to comment.