Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VQxuDBbW/1559-identify-userpref-functions-in-back-end-sdks #22

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "notificationapi-node-server-sdk",
"version": "0.17.2",
"version": "0.18.0",
"description": "NotificationAPI server-side library for Node.js",
"keywords": [
"notificationapi",
Expand Down
56 changes: 56 additions & 0 deletions src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@
Channels,
CreateSubNotificationRequest,
DeleteSubNotificationRequest,
PushProviders,
SendRequest,
SetUserPreferencesRequest,
User
} from '../interfaces';
import { createHmac } from 'crypto';

const axiosMock = new MockAdapter(axios);
const restoreConsole = mockConsole();
Expand Down Expand Up @@ -60,7 +62,7 @@
notificationapi.init(clientId, clientSecret);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const promise = notificationapi[func](params);

Check warning on line 65 in src/__tests__/index.test.ts

View workflow job for this annotation

GitHub Actions / Pull Request Pipeline

Function Call Object Injection Sink
expect(promise).toBeInstanceOf(Promise);
});

Expand All @@ -72,7 +74,7 @@
notificationapi.init(clientId, clientSecret);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await notificationapi[func](params);

Check warning on line 77 in src/__tests__/index.test.ts

View workflow job for this annotation

GitHub Actions / Pull Request Pipeline

Function Call Object Injection Sink
expect(axiosMock.history.post).toHaveLength(1);
});

Expand All @@ -85,7 +87,7 @@
notificationapi.init(clientId, clientSecret);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await notificationapi[func](params);

Check warning on line 90 in src/__tests__/index.test.ts

View workflow job for this annotation

GitHub Actions / Pull Request Pipeline

Function Call Object Injection Sink
expect(axiosMock.history.post).toHaveLength(1);
expect(axiosMock.history.post[0].headers['Authorization']).toEqual(
'Basic ' + cred
Expand All @@ -102,7 +104,7 @@
notificationapi.init(clientId, clientSecret);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await notificationapi[func](params);

Check warning on line 107 in src/__tests__/index.test.ts

View workflow job for this annotation

GitHub Actions / Pull Request Pipeline

Function Call Object Injection Sink
expect(console.log).toHaveBeenCalledTimes(1);
});

Expand All @@ -116,7 +118,7 @@
notificationapi.init(clientId, clientSecret);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return notificationapi[func](params).catch(() => {

Check warning on line 121 in src/__tests__/index.test.ts

View workflow job for this annotation

GitHub Actions / Pull Request Pipeline

Function Call Object Injection Sink
expect(console.error).toHaveBeenCalledTimes(1);
});
});
Expand All @@ -131,7 +133,7 @@
notificationapi.init(clientId, clientSecret);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
expect(notificationapi[func](params)).rejects.toEqual(

Check warning on line 136 in src/__tests__/index.test.ts

View workflow job for this annotation

GitHub Actions / Pull Request Pipeline

Function Call Object Injection Sink
new Error('Request failed with status code 500')
);
});
Expand Down Expand Up @@ -502,3 +504,57 @@
);
});
});
describe('Identify user', () => {
const userEndPointRegex = /.*\/users\/.*/;
const clientId = 'testClientId_identify_user';
const clientSecret = 'testClientSecret_identify_user';
const userId = 'testUserId_identify_user';
const user: User = {
id: userId,
email: '[email protected]',
number: '+15005550006',
pushTokens: [
{
type: PushProviders.FCM,
token: 'samplePushToken',
device: {
app_id: 'sample_app_id',
ad_id: 'sample_ad_id',
device_id: 'sample_device_id',
platform: 'sample_platform',
manufacturer: 'sample_manufacturer',
model: 'sample_model'
}
}
],
webPushTokens: [
{
sub: {
endpoint: 'sample_endpoint',
keys: {
p256dh: 'sample_p256dh',
auth: 'sample_auth'
}
}
}
]
};
test('makes API calls with a correct request body', async () => {
axiosMock.onPost(userEndPointRegex).reply(200);
await notificationapi.init(clientId, clientSecret);
await notificationapi.identifyUser(user);
const { id, ...userData } = user;
expect(axiosMock.history.post).toHaveLength(1);
expect(axiosMock.history.post[0].data).toEqual(JSON.stringify(userData));
expect(axiosMock.history.post[0].url).toEqual(
`https://api.notificationapi.com/${clientId}/users/${id}`
);
expect(axiosMock.history.post[0].headers.Authorization).toEqual(
`Basic ${Buffer.from(
`${clientId}:${userId}:${createHmac('sha256', clientSecret)
.update(`${userId}`)
.digest('base64')}`
).toString('base64')}`
);
});
});
35 changes: 28 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
InitConfiguration,
RetractRequest,
SendRequest,
SetUserPreferencesRequest
SetUserPreferencesRequest,
User
} from './interfaces';
import axios, { AxiosResponse, Method } from 'axios';
import { createHmac } from 'crypto';

const DEFAULT_BASE_URL = 'https://api.notificationapi.com';

Expand Down Expand Up @@ -37,6 +39,24 @@ class NotificationAPI {
this.clientId = clientId;
this.clientSecret = clientSecret;
};
/** used to identify your user */
identifyUser = async (user: User): Promise<AxiosResponse> => {
const { id, ...userData } = user;
const hashedUserId = `${createHmac('sha256', this.clientSecret as string)
.update(id)
.digest('base64')}`;

const customAuthorization =
'Basic ' +
Buffer.from(`${this.clientId}:${id}:${hashedUserId}`).toString('base64');

return this.request(
'POST',
`users/${encodeURIComponent(id)}`,
userData,
customAuthorization
);
};
/** Used to send a notification to the specified user. */
send = async (sendRequest: SendRequest): Promise<AxiosResponse> => {
return this.request('POST', 'sender', sendRequest);
Expand Down Expand Up @@ -77,19 +97,20 @@ class NotificationAPI {
request = async (
method: Method,
uri: string,
data?: unknown
data?: unknown,
customAuthorization?: string
): Promise<AxiosResponse> => {
const authorization: string =
customAuthorization ??
'Basic ' +
Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
try {
const res = await axios.request({
method,
url: `${this.baseURL}/${this.clientId}/${uri}`,
data,
headers: {
Authorization:
'Basic ' +
Buffer.from(`${this.clientId}:${this.clientSecret}`).toString(
'base64'
)
Authorization: authorization
}
});
if (res.status === 202) {
Expand Down
54 changes: 54 additions & 0 deletions src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export interface User {
email?: string;
/** Required for SMS/CALL notifications, otherwise optional. Valid format: +15005550006. Unformatted US/Canada numbers are also accepted, e.g., (415) 555-1212, 415-555-1212, or 4155551212.*/
number?: string;
/**Tokens which are required to send mobile push notifications, a user can have multiple devices and a push token is required for each device*/
pushTokens?: PushToken[];
/**Tokens which are required to send web push notification, a user can have multiple devices and a web push token is required for each device */
webPushTokens?: WebPushToken[];
}

export interface SendRequest {
Expand Down Expand Up @@ -95,3 +99,53 @@ export interface InitConfiguration {
/** To updated the based url. Optional.*/
baseURL?: string;
}

export interface PushToken {
/**[apn | fcm] The provider token is to be associated with. */
type: PushProviders;
/**The full token string. */
token: string;
/**Information about the device the token is associated with. */
device: Device;
}

export enum PushProviders {
/**firebase-fcm token provider */
FCM = 'FCM',
/**APN token provider */
APN = 'APN'
}

export interface Device {
/**Id of the application the token is used for */
app_id?: string;
/**Id of the advertising identifier */
ad_id?: string;
/**Id of the device the token is associated with */
device_id: string;
/**The device platform i.e. android, ios*/
platform?: string;
/**The device manufacturer */
manufacturer?: string;
/**The device model */
model?: string;
}

/**
* Configuration for a Push Subscription. This can be obtained on the frontend by calling
* serviceWorkerRegistration.pushManager.subscribe().
* The expected format is the same output as JSON.stringify'ing a PushSubscription in the browser.
*/
export interface PushSubscription {
/**a string value containing the endpoint associated with the push subscription. */
endpoint: string;
keys: {
/**An Elliptic curve Diffie–Hellman public key on the P-256 curve (that is, the NIST secp256r1 elliptic curve). The resulting key is an uncompressed point in ANSI X9.62 format. */
p256dh: string;
/**An authentication secret, as described in Message Encryption for Web Push. */
auth: string;
};
}
export interface WebPushToken {
sub: PushSubscription;
}
Loading