Skip to content

Commit

Permalink
feat(userToken): generate in-memory anonymous user tokens by default (#…
Browse files Browse the repository at this point in the history
…441)

This PR introduces a new initialization parameter: `anonymousUserToken`.
This boolean (set to `true` by default) generates an in-memory anonymous
user token which is sent with every Insights event.

See [FX-2303](https://algolia.atlassian.net/browse/FX-2303) for more
information.

[FX-2303]:
https://algolia.atlassian.net/browse/FX-2303?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ

---------

Co-authored-by: Haroen Viaene <[email protected]>
  • Loading branch information
dhayab and Haroenv authored Apr 20, 2023
1 parent edd2b3a commit 161c933
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 14 deletions.
13 changes: 5 additions & 8 deletions lib/__tests__/_sendEvent.node.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@
* @jest-environment node
*/
import AlgoliaAnalytics from "../insights";
import { getRequesterForNode } from "../utils/getRequesterForNode";
import { getFunctionalInterface } from "../_getFunctionalInterface";
import { setUserToken } from "../_tokenUtils";
import { version } from "../../package.json";

const credentials = {
apiKey: "testKey",
appId: "testId"
};

const defaultPayload = {
eventName: "my-event",
index: "my-index",
Expand All @@ -27,7 +20,11 @@ describe("_sendEvent in node env", () => {
requestFn = jest.fn((url, data) => {});
const instance = new AlgoliaAnalytics({ requestFn });
aa = getFunctionalInterface(instance);
aa("init", credentials);
aa("init", {
apiKey: "testKey",
appId: "testId",
anonymousUserToken: false
});
});

it("does not throw when user token is not set", () => {
Expand Down
108 changes: 104 additions & 4 deletions lib/__tests__/_sendEvent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,14 +346,14 @@ describe("sendEvents", () => {
});
});

describe("userToken", () => {
let analyticsInstance;
describe("customer-defined userToken", () => {
let analyticsInstance: AlgoliaAnalytics;
beforeEach(() => {
analyticsInstance = setupInstance();
});

it("should add a userToken if not provided", () => {
(analyticsInstance as any).sendEvents([
analyticsInstance.sendEvents([
{
eventType: "click",
eventName: "my-event",
Expand All @@ -372,7 +372,7 @@ describe("sendEvents", () => {
});
});
it("should pass over provided userToken", () => {
(analyticsInstance as any).sendEvents([
analyticsInstance.sendEvents([
{
eventType: "click",
eventName: "my-event",
Expand All @@ -393,6 +393,106 @@ describe("sendEvents", () => {
});
});

describe("in-memory userToken", () => {
let analyticsInstance: AlgoliaAnalytics;
beforeEach(() => {
analyticsInstance = new AlgoliaAnalytics({
requestFn: getRequesterForBrowser()
});
});

it("should be added by default", () => {
expect(analyticsInstance._anonymousUserToken).toBe(true);

analyticsInstance.sendEvents(
[
{
eventType: "click",
eventName: "my-event",
index: "my-index",
objectIDs: ["1"]
}
],
{
headers: {
"X-Algolia-Application-Id": "algoliaAppId",
"X-Algolia-API-Key": "algoliaApiKey"
}
}
);
expect(XMLHttpRequest.send).toHaveBeenCalledTimes(1);
const payload = JSON.parse(XMLHttpRequest.send.mock.calls[0][0]);
expect(payload).toEqual({
events: [
expect.objectContaining({
userToken: expect.stringMatching(/^anonymous-/)
})
]
});
});

it("should not be added if anonymousUserToken: false", () => {
analyticsInstance.init({ anonymousUserToken: false });
expect(analyticsInstance._anonymousUserToken).toBe(false);

analyticsInstance.sendEvents(
[
{
eventType: "click",
eventName: "my-event",
index: "my-index",
objectIDs: ["1"]
}
],
{
headers: {
"X-Algolia-Application-Id": "algoliaAppId",
"X-Algolia-API-Key": "algoliaApiKey"
}
}
);
expect(XMLHttpRequest.send).toHaveBeenCalledTimes(1);
const payload = JSON.parse(XMLHttpRequest.send.mock.calls[0][0]);
expect(payload).toEqual({
events: [
expect.not.objectContaining({
userToken: expect.any(String)
})
]
});
});

it("should not be added if a token is already set", () => {
analyticsInstance.setUserToken("my-user-token");

analyticsInstance.sendEvents(
[
{
eventType: "click",
eventName: "my-event",
index: "my-index",
objectIDs: ["1"]
}
],
{
headers: {
"X-Algolia-Application-Id": "algoliaAppId",
"X-Algolia-API-Key": "algoliaApiKey"
}
}
);
expect(XMLHttpRequest.send).toHaveBeenCalledTimes(1);
const payload = JSON.parse(XMLHttpRequest.send.mock.calls[0][0]);
expect(payload).toEqual({
events: [
expect.objectContaining({
userToken: "my-user-token"
})
]
});
});
});

describe("filters", () => {
let analyticsInstance;
beforeEach(() => {
Expand Down
4 changes: 4 additions & 0 deletions lib/_sendEvent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ export function makeSendEvents(requestFn: RequestFnType) {
);
}

if (!this._userToken && this._anonymousUserToken) {
this.setAnonymousUserToken(true);
}

const events: InsightsEvent[] = eventData.map((data) => {
const { filters, ...rest } = data;

Expand Down
8 changes: 7 additions & 1 deletion lib/_tokenUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,16 @@ export const getCookie = (name: string): string => {
return "";
};

export function setAnonymousUserToken(): void {
export function setAnonymousUserToken(inMemory = false): void {
if (inMemory) {
this.setUserToken(`anonymous-${createUUID()}`);
return;
}

if (!supportsCookies()) {
return;
}

const foundToken = getCookie(COOKIE_KEY);
if (
!foundToken ||
Expand Down
9 changes: 8 additions & 1 deletion lib/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface InitParams {
apiKey?: string;
appId?: string;
userHasOptedOut?: boolean;
anonymousUserToken?: boolean;
useCookie?: boolean;
cookieDuration?: number;
region?: InsightRegion;
Expand Down Expand Up @@ -56,6 +57,7 @@ You can visit https://algolia.com/events/debugger instead.`);
_userHasOptedOut: !!options.userHasOptedOut,
_region: options.region,
_host: options.host,
_anonymousUserToken: options.anonymousUserToken ?? true,
_useCookie: options.useCookie ?? false,
_cookieDuration: options.cookieDuration || 6 * MONTH
});
Expand All @@ -78,7 +80,12 @@ You can visit https://algolia.com/events/debugger instead.`);

type ThisParams = Pick<
AlgoliaAnalytics,
"_userHasOptedOut" | "_useCookie" | "_cookieDuration" | "_region" | "_host"
| "_userHasOptedOut"
| "_anonymousUserToken"
| "_useCookie"
| "_cookieDuration"
| "_region"
| "_host"
>;

function setOptions(
Expand Down
1 change: 1 addition & 0 deletions lib/insights.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class AlgoliaAnalytics {
_region: string;
_host: string;
_endpointOrigin = "https://insights.algolia.io";
_anonymousUserToken = true;
_userToken: string;
_userHasOptedOut = false;
_useCookie = false;
Expand Down

0 comments on commit 161c933

Please sign in to comment.