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

feat (core): user session #637

Merged
merged 32 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
d6c8703
Feature: User session
MoumitaM Sep 1, 2022
e42d3e8
clear session on reset API call
MoumitaM Sep 2, 2022
28cc178
manual session updated
MoumitaM Sep 2, 2022
8517b42
reset logic for user session updated
MoumitaM Sep 2, 2022
4b71fa4
code refactoring
MoumitaM Sep 5, 2022
330dba8
type declaration updated
MoumitaM Sep 5, 2022
732ff39
type declaration updated
MoumitaM Sep 5, 2022
e34cfb0
refactor(decoupling): user session
MoumitaM Sep 6, 2022
f6c422f
refactor: user session
MoumitaM Sep 6, 2022
07e7948
Merge branch 'v1-staging' into feature/user-session-v1
MoumitaM Sep 6, 2022
d60f272
Update session/index.js
MoumitaM Sep 6, 2022
7aa8aaa
refactor: user session
MoumitaM Sep 6, 2022
1ac3cf0
Merge branch 'feature/user-session-v1' of github.com:rudderlabs/rudde…
MoumitaM Sep 6, 2022
502567b
Update session/index.js
MoumitaM Sep 6, 2022
527ad40
Update analytics.js
MoumitaM Sep 6, 2022
0eb7322
refactor: user session
MoumitaM Sep 6, 2022
fdcb0e2
refactor: user session
MoumitaM Sep 7, 2022
afb54a5
refactor: user session
MoumitaM Sep 7, 2022
e143a52
Merge branch 'v1-staging' into feature/user-session-v1
MoumitaM Sep 7, 2022
8541be4
Changes: user session- session id type updated
MoumitaM Sep 19, 2022
496c1cf
Merge branch 'v1-staging' into feature/user-session-v1
MoumitaM Sep 19, 2022
efa45d0
Update session/index.js
MoumitaM Sep 19, 2022
a41919d
Update session/index.js
MoumitaM Sep 19, 2022
cfd5c85
Update session/index.js
MoumitaM Sep 19, 2022
ddb5529
refactor: user session
MoumitaM Sep 19, 2022
d1faf11
Update: user session - type declaration
MoumitaM Sep 20, 2022
c7eb977
Update dist/rudder-sdk-js/index.d.ts
MoumitaM Sep 20, 2022
2f0e551
refactor: user session
MoumitaM Sep 20, 2022
1ba03c1
Merge branch 'feature/user-session-v1' of github.com:rudderlabs/rudde…
MoumitaM Sep 20, 2022
e455a5e
Update session/index.js
MoumitaM Sep 20, 2022
89879cd
Error handling added: countDigits fn
MoumitaM Sep 20, 2022
6776d8e
Merge branch 'feature/user-session-v1' of github.com:rudderlabs/rudde…
MoumitaM Sep 20, 2022
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
34 changes: 34 additions & 0 deletions analytics.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import ScriptLoader from "./integrations/ScriptLoader";
import parseLinker from "./utils/linker";
import CookieConsentFactory from "./cookieConsent/CookieConsentFactory";
import * as BugsnagLib from "./metrics/error-report/Bugsnag";
import { UserSession } from "./session";

const queryDefaults = {
trait: "ajs_trait_",
Expand Down Expand Up @@ -108,6 +109,7 @@ class Analytics {
this.cookieConsentOptions = {};
// flag to indicate client integrations` ready status
this.clientIntegrationsReady = false;
this.uSession = UserSession;
}

/**
Expand Down Expand Up @@ -813,6 +815,14 @@ class Analytics {
};
}
}
// If auto/manual session tracking is enabled sessionId will be sent in the context
try {
const { sessionId, sessionStart } = this.uSession.getSessionInfo();
rudderElement.message.context.sessionId = sessionId;
if (sessionStart) rudderElement.message.context.sessionStart = true;
} catch (e) {
handleError(e);
}

this.processOptionsParam(rudderElement, options);
logger.debug(JSON.stringify(rudderElement));
Expand Down Expand Up @@ -983,6 +993,7 @@ class Analytics {
this.userTraits = {};
this.groupId = "";
this.groupTraits = {};
this.uSession.reset();
this.storage.clear(flag);
}

Expand Down Expand Up @@ -1105,6 +1116,9 @@ class Analytics {
this.sendAdblockPageOptions = options.sendAdblockPageOptions;
}
}
// Session initialization
this.uSession.initialize(options);

if (options && options.clientSuppliedCallbacks) {
// convert to rudder recognised method names
const tranformedCallbackMapping = {};
Expand Down Expand Up @@ -1351,6 +1365,22 @@ class Analytics {

return returnObj;
}

/**
* A public method to start a session
* @param {string} sessionId session identifier
* @returns
*/
startSession(sessionId) {
pallabmaiti marked this conversation as resolved.
Show resolved Hide resolved
this.uSession.start(sessionId);
}

/**
* A public method to end an ongoing session.
*/
endSession() {
this.uSession.end();
}
}

function pushQueryStringDataToAnalyticsArray(obj) {
Expand Down Expand Up @@ -1466,6 +1496,8 @@ const initialized = (instance.initialized = true);
const getUserTraits = instance.getUserTraits.bind(instance);
const getAnonymousId = instance.getAnonymousId.bind(instance);
const setAnonymousId = instance.setAnonymousId.bind(instance);
const startSession = instance.startSession.bind(instance);
const endSession = instance.endSession.bind(instance);

export {
initialized,
Expand All @@ -1480,4 +1512,6 @@ export {
getUserTraits,
getAnonymousId,
setAnonymousId,
startSession,
endSession,
};
16 changes: 16 additions & 0 deletions dist/rudder-sdk-js/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ declare module "rudder-sdk-js" {
cookieConsentManager?: cookieConsentManager;
anonymousIdOptions?: anonymousIdOptions;
sameSiteCookie?: string;
sessions?: {
saikumarrs marked this conversation as resolved.
Show resolved Hide resolved
autoTrack?: boolean; // Defaults to true
timeout?: number; // Defaults to 30 mins
}
}

/**
Expand Down Expand Up @@ -434,6 +438,16 @@ declare module "rudder-sdk-js" {
*/
function getUserId(): string;

/**
* To manually start user session in the SDK
*/
function startSession(sessionId?: number): void;

/**
* To manually end user session in the SDK
*/
function endSession(): void;

export {
integrationOptions,
loadOptions,
Expand All @@ -453,5 +467,7 @@ declare module "rudder-sdk-js" {
setAnonymousId,
getAnonymousId,
getUserId,
startSession,
endSession,
};
}
200 changes: 200 additions & 0 deletions session/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/* eslint-disable consistent-return */
/* eslint-disable class-methods-use-this */
/* eslint-disable import/prefer-default-export */
import Storage from '../utils/storage';
import logger from '../utils/logUtil';
import {
DEFAULT_SESSION_TIMEOUT,
MIN_SESSION_TIMEOUT,
MIN_SESSION_ID_LENGTH,
} from "../utils/constants";
import { handleError, countDigits } from "../utils/utils";

class UserSession {
constructor() {
this.storage = Storage;
this.timeout = DEFAULT_SESSION_TIMEOUT;
this.sessionInfo = {
autoTrack: true,
};
}

/**
* A function to initialize session information
* @param {object} options load call options
*/
initialize(options) {
try {
// Fetch session information from storage if any or initialize with an empty object
this.sessionInfo = this.storage.getSessionInfo() || this.sessionInfo;
/**
* By default this.autoTrack will be true
* Cases where this.autoTrack will be false:
* 1. User explicitly set autoTrack load option to false
* 2. When user is manually tracking the session
*
* Depending on the use case, this.autoTrack is set to true/false.
*/
this.sessionInfo.autoTrack = !(
options?.sessions?.autoTrack === false || this.sessionInfo.manualTrack
);
/**
* Validate "timeout" input. Should be provided in milliseconds.
* Session timeout: By default, a session lasts until there's 30 minutes of inactivity,
* but you can configure this limit using "timeout" load option
*/
if (options?.sessions && !isNaN(options.sessions.timeout)) {
const { timeout } = options.sessions;
// In case user provides 0 as the timeout, auto session tracking will be disabled
if (timeout === 0) {
logger.warn(
'[Session]:: Provided timeout value 0 will disable the auto session tracking feature.',
);
this.sessionInfo.autoTrack = false;
}
// In case user provides a setTimeout value greater than 0 but less than 10 seconds SDK will show a warning
// and will proceed with it
if (timeout > 0 && timeout < MIN_SESSION_TIMEOUT) {
logger.warn('[Session]:: It is not advised to set "timeout" less than 10 seconds');
}
this.timeout = timeout;
}
// If auto session tracking is enabled start the session tracking
if (this.sessionInfo.autoTrack) {
this.startAutoTracking();
} else if (
this.sessionInfo.autoTrack === false &&
!this.sessionInfo.manualTrack
) {
/**
* Use case:
* By default user session is enabled which means storage will have session data.
* In case user wanted to opt out and set auto track to false through load option,
* clear stored session info.
*/
this.end();
}
} catch (e) {
handleError(e);
}
}

/**
* A function to validate current session and return true/false depending on that
* @param {number} timestamp
* @returns boolean
*/
isValidSession(timestamp) {
return timestamp <= this.sessionInfo.expiresAt;
}

/**
* A function to generate session id
* @returns number
*/
generateSessionId() {
return Date.now();
}

/**
* A function to check for existing session details and depending on that create a new session.
*/
startAutoTracking() {
const timestamp = Date.now();
if (!this.isValidSession(timestamp)) {
this.sessionInfo = {};
this.sessionInfo.id = timestamp; // set the current timestamp
this.sessionInfo.expiresAt = timestamp + this.timeout; // set the expiry time of the session
this.sessionInfo.sessionStart = true;
this.sessionInfo.autoTrack = true;
}
this.storage.setSessionInfo(this.sessionInfo);
}

/**
* Function to validate user provided sessionId
* @param {number} sessionId
* @returns
*/
validateSessionId(sessionId) {
if (typeof sessionId !== 'number' || sessionId % 1 !== 0) {
logger.error(`[Session]:: "sessionId" should only contain neumerical digits`);
MoumitaM marked this conversation as resolved.
Show resolved Hide resolved
return;
}
if (countDigits(sessionId) < MIN_SESSION_ID_LENGTH) {
logger.error(
`[Session]:: "sessionId" should at least be "${MIN_SESSION_ID_LENGTH}" digits long`
);
return;
}
return sessionId;
}

/**
* A public method to start a session
* @param {number} sessionId session identifier
* @returns
*/
start(id) {
const sessionId = id
? this.validateSessionId(id)
: this.generateSessionId();

this.sessionInfo = {
id: sessionId || this.generateSessionId(),
sessionStart: true,
manualTrack: true,
};
this.storage.setSessionInfo(this.sessionInfo);
}

/**
* A public method to end an ongoing session.
*/
end() {
this.sessionInfo = {};
this.storage.removeSessionInfo();
}

/**
* A function get ongoing sessionId.
*/
getSessionInfo() {
const session = {};
if (this.sessionInfo.autoTrack || this.sessionInfo.manualTrack) {
// renew or create a new auto-tracking session
if (this.sessionInfo.autoTrack) {
const timestamp = Date.now();
if (!this.isValidSession(timestamp)) {
this.startAutoTracking();
} else {
this.sessionInfo.expiresAt = timestamp + this.timeout; // set the expiry time of the session
}
}

if (this.sessionInfo.sessionStart) {
session.sessionStart = true;
this.sessionInfo.sessionStart = false;
}
session.sessionId = this.sessionInfo.id;
this.storage.setSessionInfo(this.sessionInfo);
}
return session;
}

/**
* Refresh session info on reset API call
*/
reset() {
const { manualTrack, autoTrack } = this.sessionInfo;
if (autoTrack) {
this.sessionInfo = {};
this.startAutoTracking();
} else if (manualTrack) {
this.start();
}
}
}

const userSession = new UserSession();
export { userSession as UserSession };
7 changes: 7 additions & 0 deletions utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ const GENERIC_FALSE_VALUES = ["false", "False", "FALSE", "f", "F", "0"];

const SAMESITE_COOKIE_OPTS = ['Lax', 'None', 'Strict'];

const DEFAULT_SESSION_TIMEOUT = 30 * 60 * 1000; // 30 min in milliseconds
const MIN_SESSION_TIMEOUT = 10 * 1000; // 10 sec in milliseconds
const MIN_SESSION_ID_LENGTH = 10;

export {
ReservedPropertyKeywords,
MessageType,
Expand All @@ -120,6 +124,9 @@ export {
GENERIC_TRUE_VALUES,
GENERIC_FALSE_VALUES,
SAMESITE_COOKIE_OPTS,
DEFAULT_SESSION_TIMEOUT,
MIN_SESSION_TIMEOUT,
MIN_SESSION_ID_LENGTH,
};

/* module.exports = {
Expand Down
25 changes: 25 additions & 0 deletions utils/storage/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const defaults = {
group_storage_trait: "rl_group_trait",
page_storage_init_referrer: "rl_page_init_referrer",
page_storage_init_referring_domain: "rl_page_init_referring_domain",
session_info: "rl_session",
prefix: "RudderEncrypt:",
key: "Rudder",
};
Expand Down Expand Up @@ -207,6 +208,17 @@ class Storage {
);
}

/**
* Set session information
* @param {*} value
*/
setSessionInfo(value) {
this.storage.set(
defaults.session_info,
this.encryptValue(this.stringify(value))
);
}

/**
*
* @param {*} key
Expand Down Expand Up @@ -363,6 +375,15 @@ class Storage {
);
}

/**
* get the stored session info
*/
getSessionInfo() {
return this.parse(
this.decryptValue(this.storage.get(defaults.session_info))
);
}

/**
*
* @param {*} key
Expand All @@ -371,6 +392,10 @@ class Storage {
return this.storage.remove(key);
}

removeSessionInfo() {
this.removeItem(defaults.session_info);
}

/**
* remove stored keys
*/
Expand Down
Loading