UNPKG

@embrace-io/web-sdk

Version:
1 lines 6.18 kB
{"version":3,"file":"state.cjs","names":["generateUUID","EMBRACE_USER_SESSION_STATE_KEY","EMBRACE_PERMANENT_PROPERTIES_KEY"],"sources":["../../../../src/managers/EmbraceUserSessionManager/utils/state.ts"],"sourcesContent":["import type { DiagLogger } from '@opentelemetry/api';\nimport type { NamespacedStorage } from '../../../utils/index.ts';\nimport { generateUUID } from '../../../utils/index.ts';\nimport {\n EMBRACE_PERMANENT_PROPERTIES_KEY,\n EMBRACE_USER_SESSION_STATE_KEY,\n USER_SESSION_STATE_SCHEMA_VERSION,\n} from '../constants.ts';\nimport type { UserSessionState } from '../types.ts';\n\n/**\n * True when the stored state is no longer trustworthy: the device clock\n * jumped backwards past the recorded start, the max-duration deadline has\n * passed, or an inactivity deadline (set when the last part ended) has\n * passed. Inactivity only applies between parts; the deadline is null\n * while a part is active.\n */\nexport const isUserSessionExpired = (\n state: UserSessionState,\n now: number,\n): boolean =>\n now < state.userSessionStartTs ||\n now >= state.userSessionMaxEndTs ||\n (state.inactivityDeadlineTs !== null && now >= state.inactivityDeadlineTs);\n\nexport interface CreateUserSessionStateArgs {\n now: number;\n previousUserSessionId: string | null;\n userSessionMaxDurationSeconds: number;\n userSessionInactivityTimeoutSeconds: number;\n userSessionForegroundInactivityTimeoutSeconds: number;\n userSessionNumber: number;\n}\n\nexport const createUserSessionState = ({\n now,\n previousUserSessionId,\n userSessionMaxDurationSeconds,\n userSessionInactivityTimeoutSeconds,\n userSessionForegroundInactivityTimeoutSeconds,\n userSessionNumber,\n}: CreateUserSessionStateArgs): UserSessionState => ({\n schemaVersion: USER_SESSION_STATE_SCHEMA_VERSION,\n userSessionId: generateUUID(),\n previousUserSessionId,\n userSessionStartTs: now,\n userSessionMaxEndTs: now + userSessionMaxDurationSeconds * 1000,\n userSessionNumber,\n userSessionPartIndex: 0,\n userSessionMaxDurationSeconds,\n userSessionInactivityTimeoutSeconds,\n userSessionForegroundInactivityTimeoutSeconds,\n inactivityDeadlineTs: null,\n userSessionProperties: {},\n});\n\n/**\n * Treats the schemaVersion stamp as the contract. Every field is written\n * by createUserSessionState (and userSessionProperties is validated at\n * addProperty time), so a matching version means the blob came from this\n * code path and we trust the rest of its shape.\n */\nconst isValidStoredUserSessionState = (\n state: unknown,\n): state is UserSessionState =>\n state !== null &&\n typeof state === 'object' &&\n (state as { schemaVersion?: unknown }).schemaVersion ===\n USER_SESSION_STATE_SCHEMA_VERSION;\n\nexport const readUserSessionState = (\n storage: NamespacedStorage,\n diag: DiagLogger,\n): UserSessionState | null => {\n const raw = storage.getItem(EMBRACE_USER_SESSION_STATE_KEY);\n if (!raw) {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n if (!isValidStoredUserSessionState(parsed)) {\n throw new Error('stored user session state failed structural validation');\n }\n return parsed;\n } catch (e) {\n diag.error('User session state in storage is corrupt; discarding it', e);\n storage.removeItem(EMBRACE_USER_SESSION_STATE_KEY);\n return null;\n }\n};\n\nexport const readPermanentProperties = (\n storage: NamespacedStorage,\n diag: DiagLogger,\n): Record<string, string> => {\n const raw = storage.getItem(EMBRACE_PERMANENT_PROPERTIES_KEY);\n if (!raw) {\n return {};\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n if (\n parsed === null ||\n typeof parsed !== 'object' ||\n Array.isArray(parsed)\n ) {\n throw new Error('permanent properties blob is not a plain object');\n }\n const validated: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (typeof value === 'string') {\n validated[key] = value;\n }\n }\n return validated;\n } catch (e) {\n diag.error('Permanent properties in storage are corrupt; discarding', e);\n storage.removeItem(EMBRACE_PERMANENT_PROPERTIES_KEY);\n return {};\n }\n};\n\n/**\n * Empty blobs are removed rather than written so storage stays tidy when\n * the last property is cleared. Returns false on a failed write; callers\n * gate cross-scope flips on this so a failed permanent write doesn't\n * strip the previously-visible user-session-scoped value.\n */\nexport const storePermanentProperties = (\n storage: NamespacedStorage,\n properties: Record<string, string>,\n): boolean => {\n if (Object.keys(properties).length === 0) {\n return storage.removeItem(EMBRACE_PERMANENT_PROPERTIES_KEY);\n }\n return storage.setItem(\n EMBRACE_PERMANENT_PROPERTIES_KEY,\n JSON.stringify(properties),\n );\n};\n"],"mappings":";;;;;;;;;;;AAiBA,MAAa,wBACX,OACA,QAEA,MAAM,MAAM,sBACZ,OAAO,MAAM,uBACZ,MAAM,yBAAyB,QAAQ,OAAO,MAAM;AAWvD,MAAa,0BAA0B,EACrC,KACA,uBACA,+BACA,qCACA,+CACA,yBACmD;CACnD,eAAA;CACA,eAAeA,2BAAAA,aAAa;CAC5B;CACA,oBAAoB;CACpB,qBAAqB,MAAM,gCAAgC;CAC3D;CACA,sBAAsB;CACtB;CACA;CACA;CACA,sBAAsB;CACtB,uBAAuB,CAAC;AAC1B;;;;;;;AAQA,MAAM,iCACJ,UAEA,UAAU,QACV,OAAO,UAAU,YAChB,MAAsC,kBAAA;AAGzC,MAAa,wBACX,SACA,SAC4B;CAC5B,MAAM,MAAM,QAAQ,QAAQC,qDAAAA,8BAA8B;CAC1D,IAAI,CAAC,KACH,OAAO;CAET,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,GAAG;EACtC,IAAI,CAAC,8BAA8B,MAAM,GACvC,MAAM,IAAI,MAAM,wDAAwD;EAE1E,OAAO;CACT,SAAS,GAAG;EACV,KAAK,MAAM,2DAA2D,CAAC;EACvE,QAAQ,WAAWA,qDAAAA,8BAA8B;EACjD,OAAO;CACT;AACF;AAEA,MAAa,2BACX,SACA,SAC2B;CAC3B,MAAM,MAAM,QAAQ,QAAQC,qDAAAA,gCAAgC;CAC5D,IAAI,CAAC,KACH,OAAO,CAAC;CAEV,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IACE,WAAW,QACX,OAAO,WAAW,YAClB,MAAM,QAAQ,MAAM,GAEpB,MAAM,IAAI,MAAM,iDAAiD;EAEnE,MAAM,YAAoC,CAAC;EAC3C,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,GAC9C,IAAI,OAAO,UAAU,UACnB,UAAU,OAAO;EAGrB,OAAO;CACT,SAAS,GAAG;EACV,KAAK,MAAM,2DAA2D,CAAC;EACvE,QAAQ,WAAWA,qDAAAA,gCAAgC;EACnD,OAAO,CAAC;CACV;AACF;;;;;;;AAQA,MAAa,4BACX,SACA,eACY;CACZ,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,WAAW,GACrC,OAAO,QAAQ,WAAWA,qDAAAA,gCAAgC;CAE5D,OAAO,QAAQ,QACbA,qDAAAA,kCACA,KAAK,UAAU,UAAU,CAC3B;AACF"}