Skip to content

Commit

Permalink
[C-6392] Add support for token management (#100)
Browse files Browse the repository at this point in the history
* WIP

* Rename users folder

* Add tests for tokenManagement

* Update changelog
  • Loading branch information
drew-y committed Jul 18, 2022
1 parent fb5258a commit b98581f
Show file tree
Hide file tree
Showing 7 changed files with 249 additions and 1 deletion.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased][unreleased]

## [3.14.0]

- adds support for token management

## [3.13.1] - 2022-06-03

- adds provider and channel timeout
Expand Down
2 changes: 1 addition & 1 deletion jestconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"]
}
}
99 changes: 99 additions & 0 deletions src/__tests__/token-management.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { CourierClient } from "..";
import { UserToken } from "../token-management/types";

const mock = new MockAdapter(axios);
const mockToken: UserToken = {
token: "abc",
provider_key: "apn",
};

describe("CourierAudiences", () => {
beforeEach(() => {
jest.clearAllMocks();
mock.reset();
});

const { tokenManagement } = CourierClient({
authorizationToken: "AUTH_TOKEN",
});

describe("putUserTokens", () => {
it("resolves void when the put call succeeds", async () => {
expect.assertions(1);

mock.onPut("/users/me/tokens").reply(204);
const prom = tokenManagement.putUserTokens({
user_id: "me",
tokens: [mockToken],
});
await expect(prom).resolves.not.toThrow();
});
});

describe("putUserToken", () => {
it("resolves void when the put call succeeds", async () => {
expect.assertions(1);

mock.onPut("/users/me/tokens/abc").reply(204);
const prom = tokenManagement.putUserToken({
user_id: "me",
token: mockToken,
});
await expect(prom).resolves.not.toThrow();
});
});

describe("patchUserToken", () => {
it("resolves void when the put call succeeds", async () => {
expect.assertions(1);

mock.onPatch("/users/me/tokens/abc").reply(204);
const prom = tokenManagement.patchUserToken({
user_id: "me",
token: "abc",
patch: [{ op: "replace", path: "status", value: "revoked" }],
});
await expect(prom).resolves.not.toThrow();
});
});

describe("getUserToken", () => {
it("resolves with the token when the get call succeeds", async () => {
expect.assertions(1);

mock.onGet("/users/me/tokens/abc").reply(200, mockToken);
const result = await tokenManagement.getUserToken({
user_id: "me",
token: "abc",
});
expect(result).toMatchObject(mockToken);
});
});

describe("getUserTokens", () => {
it("resolves with the tokens when the get call succeeds", async () => {
expect.assertions(1);

mock.onGet("/users/me/tokens").reply(200, { tokens: [mockToken] });
const result = await tokenManagement.getUserTokens({
user_id: "me",
});
expect(result).toMatchObject({ tokens: [mockToken] });
});
});

describe("deleteUserToken", () => {
it("resolves with the token when the delete call succeeds", async () => {
expect.assertions(1);

mock.onDelete("/users/me/tokens/abc").reply(204);
const prom = tokenManagement.deleteUserToken({
user_id: "me",
token: "abc",
});
await expect(prom).resolves.not.toThrow();
});
});
});
2 changes: 2 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
replaceProfile,
} from "./profile";
import { send } from "./send";
import { tokenManagement } from "./token-management";

import {
ICourierClient,
Expand Down Expand Up @@ -113,5 +114,6 @@ export const client = (
replaceBrand: replaceBrand(options),
replaceProfile: replaceProfile(options),
send: send(options),
tokenManagement: tokenManagement(options),
};
};
75 changes: 75 additions & 0 deletions src/token-management/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ICourierClientConfiguration } from "../types";
import {
DeleteUserTokenOpts,
GetUserTokenOpts,
GetUserTokenResponse,
GetUserTokensOpts,
PatchUserTokenOpts,
PutUserTokenOpts,
PutUserTokensOpts,
} from "./types";

/** Associate a group of tokens with the supplied :user_id. Will overwrite any existing tokens associated with that user. */
const putUserTokens = (options: ICourierClientConfiguration) => {
return async (opts: PutUserTokensOpts): Promise<void> => {
await options.httpClient.put(`/users/${opts.user_id}/tokens`, {
tokens: opts.tokens,
});
};
};

/** Associate a token with the supplied :user_id. If token exists it's value will be replaced with the passed body, otherwise the token will be created. */
const putUserToken = (options: ICourierClientConfiguration) => {
return async (opts: PutUserTokenOpts): Promise<void> => {
await options.httpClient.put(
`/users/${opts.user_id}/tokens/${opts.token.token}`,
opts.token
);
};
};

const patchUserToken = (options: ICourierClientConfiguration) => {
return async (opts: PatchUserTokenOpts): Promise<void> => {
await options.httpClient.patch(
`/users/${opts.user_id}/tokens/${opts.token}`,
{ patch: opts.patch }
);
};
};

const getUserToken = (options: ICourierClientConfiguration) => {
return async (opts: GetUserTokenOpts): Promise<GetUserTokenResponse> => {
const res = await options.httpClient.get(
`/users/${opts.user_id}/tokens/${opts.token}`
);
return res.data as GetUserTokenResponse;
};
};

const getUserTokens = (options: ICourierClientConfiguration) => {
return async (
opts: GetUserTokensOpts
): Promise<{ tokens: GetUserTokenResponse[] }> => {
const res = await options.httpClient.get(`/users/${opts.user_id}/tokens`);
return res.data as { tokens: GetUserTokenResponse[] };
};
};

const deleteUserToken = (options: ICourierClientConfiguration) => {
return async (opts: DeleteUserTokenOpts): Promise<void> => {
await options.httpClient.delete(
`/users/${opts.user_id}/tokens/${opts.token}`
);
};
};

export const tokenManagement = (options: ICourierClientConfiguration) => {
return {
putUserTokens: putUserTokens(options),
putUserToken: putUserToken(options),
patchUserToken: patchUserToken(options),
getUserToken: getUserToken(options),
getUserTokens: getUserTokens(options),
deleteUserToken: deleteUserToken(options),
};
};
66 changes: 66 additions & 0 deletions src/token-management/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export interface UserToken {
token: string;

provider_key: "firebase-fcm" | "apn" | "expo" | "onesignal";

/** ISO 8601 Date. Set to false to disable expiration */
expiry_date?: string | false;

/** Additional properties to be passed to provider or to be generically associated with the token */
properties?: { [key: string]: any };

device?: {
app_id?: string;
ad_id?: string;
device_id?: string;
platform?: string;
manufacturer?: string;
model?: string;
};

tracking?: {
os_version?: string;
ip?: string;
lat?: string;
long?: string;
};
}

export interface GetUserTokenResponse extends UserToken {
status: "active" | "unknown" | "failed" | "revoked";
status_reason?: string;
}

export interface PutUserTokensOpts {
user_id: string;
tokens: UserToken[];
}

export interface PutUserTokenOpts {
user_id: string;
token: UserToken;
}

export interface PatchUserTokenOpts {
user_id: string;
token: string;
patch: {
op: "replace" | "add" | "remove" | "copy" | "move" | "test";
path: string;
value?: string;
}[];
}

export interface GetUserTokenOpts {
user_id: string;
token: string;
}

export interface GetUserTokensOpts {
user_id: string;
}

export interface DeleteUserTokenOpts {
user_id: string;
token: string;
}
2 changes: 2 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IRecipientPreferences,
} from "./preferences/types";
import { Message } from "./send/types";
import { tokenManagement } from "./token-management";

export type HttpMethodClient = <T>(
url: string,
Expand Down Expand Up @@ -442,4 +443,5 @@ export interface ICourierClient {
params: T,
config?: ICourierSendConfig
) => Promise<SendResponse<T>>;
tokenManagement: ReturnType<typeof tokenManagement>;
}

0 comments on commit b98581f

Please sign in to comment.