Skip to content

Commit

Permalink
Feature: Auto capture anonymous id (#497)
Browse files Browse the repository at this point in the history
* auto capture segment's anon id

* Update utils/storage/storage.js

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>

* Update utils/storage/storage.js

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>

* Update utils/storage/storage.js

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>

* code refactoring

* added checking for cookie support existance

* made the key case insensitive

* load option structure modified

* updated type declaration

* Update utils/storage/storage.js

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>

* code refactoring

* code refactoring

* code refactoring

* ref link added

* used get npm package

* Update utils/storage/storage.js

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>

* code refactoring & comment added

* comment added

* code refactored and removed unused code

* minor modifications

* Update utils/storage/storage.js

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>

* code refactoring

* code refactoring

Co-authored-by: Sai Kumar Battinoju <88789928+saikumarrs@users.noreply.github.com>
  • Loading branch information
MoumitaM and saikumarrs authored Apr 25, 2022
1 parent 8b0e28f commit e4fa754
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 18 deletions.
10 changes: 5 additions & 5 deletions analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class Analytics {
/**
* initialize the user after load config
*/
initializeUser() {
initializeUser(anonymousIdOptions) {
this.userId =
this.storage.getUserId() != undefined ? this.storage.getUserId() : "";

Expand All @@ -123,7 +123,7 @@ class Analytics {
? this.storage.getGroupTraits()
: {};

this.anonymousId = this.getAnonymousId();
this.anonymousId = this.getAnonymousId(anonymousIdOptions);

// save once for storing older values to encrypted
this.storage.setUserId(this.userId);
Expand Down Expand Up @@ -936,9 +936,9 @@ class Analytics {
this.storage.clear(flag);
}

getAnonymousId() {
getAnonymousId(anonymousIdOptions) {
// if (!this.loaded) return;
this.anonymousId = this.storage.getAnonymousId();
this.anonymousId = this.storage.getAnonymousId(anonymousIdOptions);
if (!this.anonymousId) {
this.setAnonymousId();
}
Expand Down Expand Up @@ -1072,7 +1072,7 @@ class Analytics {
}

this.eventRepository.initialize(writeKey, serverUrl, options);
this.initializeUser();
this.initializeUser(options ? options.anonymousIdOptions : undefined);
this.setInitialPageProperties();
this.loaded = true;
if (
Expand Down
14 changes: 13 additions & 1 deletion dist/rudder-sdk-js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ declare module "rudder-sdk-js" {
};
}

/**
* Represents the options parameter for anonymousId
*/
interface anonymousIdOptions {
autoCapture?: {
enabled?: boolean;
source?: string;
};
}

/**
* Represents the options parameter in the load API
*/
Expand All @@ -74,6 +84,7 @@ declare module "rudder-sdk-js" {
useBeacon?: boolean; // Defaults to false
beaconQueueOptions?: beaconQueueOptions;
cookieConsentManager?: cookieConsentManager;
anonymousIdOptions?: anonymousIdOptions;
}

/**
Expand Down Expand Up @@ -399,7 +410,7 @@ declare module "rudder-sdk-js" {
/**
* To get anonymousId set in the SDK
*/
function getAnonymousId(): string;
function getAnonymousId(options?: anonymousIdOptions): string;

/**
* To set anonymousId
Expand Down Expand Up @@ -429,6 +440,7 @@ declare module "rudder-sdk-js" {
queueOptions,
apiObject,
apiCallback,
anonymousIdOptions,
load,
ready,
reset,
Expand Down
23 changes: 16 additions & 7 deletions utils/storage/cookie.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class CookieLocal {
constructor(options) {
this._options = {};
this.options(options);
this.isSupportAvailable = this.checkSupportAvailability();
}

/**
Expand All @@ -31,13 +32,6 @@ class CookieLocal {
domain,
samesite: "Lax",
});

// try setting a cookie first
this.set("test_rudder", true);
if (!this.get("test_rudder")) {
this._options.domain = null;
}
this.remove("test_rudder");
}

/**
Expand Down Expand Up @@ -75,6 +69,21 @@ class CookieLocal {
return false;
}
}

/**
* Function to check cookie support exists or not
* @returns boolean
*/
checkSupportAvailability() {
const name = "test_rudder_cookie";
this.set(name, true);

if (this.get(name)) {
this.remove(name);
return true;
}
return false;
}
}

// Exporting only the instance
Expand Down
96 changes: 91 additions & 5 deletions utils/storage/storage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable class-methods-use-this */
import AES from "crypto-js/aes";
import Utf8 from "crypto-js/enc-utf8";
import get from "get-value";
import logger from "../logUtil";
import { Cookie } from "./cookie";
import { Store } from "./store";
Expand All @@ -17,16 +18,18 @@ const defaults = {
key: "Rudder",
};

const anonymousIdKeyMap = {
segment: "ajs_anonymous_id",
};

/**
* An object that handles persisting key-val from Analytics
*/
class Storage {
constructor() {
// First try setting the storage to cookie else to localstorage
Cookie.set("rudder_cookies", true);

if (Cookie.get("rudder_cookies")) {
Cookie.remove("rudder_cookies");
if (Cookie.isSupportAvailable) {
this.storage = Cookie;
return;
}
Expand Down Expand Up @@ -248,13 +251,96 @@ class Storage {
);
}

/**
* Function to fetch anonymousId from external source
* @param {string} key source of the anonymousId
* @returns string
*/
fetchExternalAnonymousId(source) {
let anonId;
const key = source.toLowerCase();
if (!Object.keys(anonymousIdKeyMap).includes(key)) {
return anonId;
}
switch (key) {
case "segment":
/**
* First check the local storage for anonymousId
* Ref: https://segment.com/docs/connections/sources/catalog/libraries/website/javascript/#identify
*/
if (Store.enabled) {
anonId = Store.get(anonymousIdKeyMap[key]);
}
// If anonymousId is not present in local storage and check cookie support exists
// fetch it from cookie
if (!anonId && Cookie.IsCookieSupported()) {
anonId = Cookie.get(anonymousIdKeyMap[key]);
}
return anonId;

default:
return anonId;
}
}

/**
* get stored anonymous id
*
* Use cases:
* 1. getAnonymousId() -> anonymousIdOptions is undefined this function will return rl_anonymous_id
* if present otherwise undefined
*
* 2. getAnonymousId(anonymousIdOptions) -> In case anonymousIdOptions is present this function will check
* if rl_anonymous_id is present then it will return that
*
* otherwise it will validate the anonymousIdOptions and try to fetch the anonymous Id from the provided source.
* Finally if no anonymous Id is present in the source it will return undefined.
*
* anonymousIdOptions example:
* {
autoCapture: {
enabled: true,
source: "segment",
},
}
*
*/
getAnonymousId() {
return this.parse(
getAnonymousId(anonymousIdOptions) {
// fetch the rl_anonymous_id from storage
const rlAnonymousId = this.parse(
this.decryptValue(this.storage.get(defaults.user_storage_anonymousId))
);
/**
* If RS's anonymous ID is available, return from here.
*
* The user, while migrating from a different analytics SDK,
* will only need to auto-capture the anonymous ID when the RS SDK
* loads for the first time.
*
* The captured anonymous ID would be available in RS's persistent storage
* for all the subsequent SDK runs.
* So, instead of always grabbing the ID from the migration source when
* the options are specified, it is first checked in the RS's persistent storage.
*
* Moreover, the user can also clear the anonymous ID from the storage via
* the 'reset' API, which renders the migration source's data useless.
*/
if (rlAnonymousId) {
return rlAnonymousId;
}
// validate the provided anonymousIdOptions argument
const source = get(anonymousIdOptions, "autoCapture.source");
if (
get(anonymousIdOptions, "autoCapture.enabled") === true &&
typeof source === "string"
) {
// fetch the anonymousId from the external source
// ex - segment
const anonId = this.fetchExternalAnonymousId(source);
if (anonId) return anonId; // return anonymousId if present
}

return rlAnonymousId; // return undefined
}

/**
Expand Down

0 comments on commit e4fa754

Please sign in to comment.