From 1160129c89799d2e139b0df20264316ea81f5b56 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 15 Oct 2026 15:30:36 -0700 Subject: [PATCH 1/5] fix(clerk-js): Preserve indefinite session deadlines (Core 2) Backport of #9871. Keep expire_at and abandon_at of 0 as Unix epoch zero for Session and SessionWithActivities instead of substituting the current time, so sessions without a maximum lifetime survive loading and snapshot restoration. The shared unixEpochToDate helper is unchanged for non-session callers. --- .changeset/native-session-lifetime-core-2.md | 5 ++++ .../clerk-js/src/core/resources/Session.ts | 6 ++--- .../core/resources/SessionWithActivities.ts | 6 ++--- .../core/resources/__tests__/Client.test.ts | 25 +++++++++++++++++ .../core/resources/__tests__/Session.test.ts | 27 +++++++++++++++++++ .../clerk-js/src/utils/__tests__/date.test.ts | 21 ++++++++++++++- packages/clerk-js/src/utils/date.ts | 5 ++++ 7 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 .changeset/native-session-lifetime-core-2.md diff --git a/.changeset/native-session-lifetime-core-2.md b/.changeset/native-session-lifetime-core-2.md new file mode 100644 index 00000000000..e5265cb1b36 --- /dev/null +++ b/.changeset/native-session-lifetime-core-2.md @@ -0,0 +1,5 @@ +--- +'@clerk/clerk-js': patch +--- + +Keep a session's `expireAt` and `abandonAt` at Unix epoch zero when the session has no maximum lifetime, instead of replacing them with the current time. Sessions restored from a client snapshot keep these deadlines too. diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index bcd93eceffe..dd61d292aca 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -28,7 +28,7 @@ import type { } from '@clerk/shared/types'; import { isWebAuthnSupported as isWebAuthnSupportedOnWindow } from '@clerk/shared/webauthn'; -import { unixEpochToDate } from '@/utils/date'; +import { unixEpochToDate, unixEpochToSessionDeadline } from '@/utils/date'; import { debugLogger } from '@/utils/debug'; import { convertJSONToPublicKeyRequestOptions, @@ -309,8 +309,8 @@ export class Session extends BaseResource implements SessionResource { this.id = data.id; this.status = data.status; - this.expireAt = unixEpochToDate(data.expire_at); - this.abandonAt = unixEpochToDate(data.abandon_at); + this.expireAt = unixEpochToSessionDeadline(data.expire_at); + this.abandonAt = unixEpochToSessionDeadline(data.abandon_at); this.factorVerificationAge = data.factor_verification_age; this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined); this.lastActiveOrganizationId = data.last_active_organization_id; diff --git a/packages/clerk-js/src/core/resources/SessionWithActivities.ts b/packages/clerk-js/src/core/resources/SessionWithActivities.ts index 23c8ababed2..edafb16f4f0 100644 --- a/packages/clerk-js/src/core/resources/SessionWithActivities.ts +++ b/packages/clerk-js/src/core/resources/SessionWithActivities.ts @@ -6,7 +6,7 @@ import type { SessionWithActivitiesResource, } from '@clerk/shared/types'; -import { unixEpochToDate } from '../../utils/date'; +import { unixEpochToDate, unixEpochToSessionDeadline } from '../../utils/date'; import { BaseResource } from './internal'; const mapSessionActivityJSONToSessionActivity = (data: SessionActivityJSON): SessionActivity => ({ @@ -65,8 +65,8 @@ export class SessionWithActivities extends BaseResource implements SessionWithAc this.id = data.id; this.status = data.status; - this.expireAt = unixEpochToDate(data.expire_at); - this.abandonAt = unixEpochToDate(data.abandon_at); + this.expireAt = unixEpochToSessionDeadline(data.expire_at); + this.abandonAt = unixEpochToSessionDeadline(data.abandon_at); this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined); this.latestActivity = mapSessionActivityJSONToSessionActivity(data.latest_activity ?? ({} as SessionActivityJSON)); this.actor = data.actor; diff --git a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts index dd024d128ea..1f86440b938 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts @@ -304,6 +304,31 @@ describe('Client Singleton', () => { }); }); + it('restores a session without a maximum lifetime from a client snapshot', () => { + const user = createUser({ first_name: 'John', last_name: 'Doe', id: 'user_1' }); + const clientJSON: ClientJSON = { + object: 'client', + id: 'test_id', + status: 'active', + last_active_session_id: 'session_1', + sign_in: null, + sign_up: null, + sessions: [{ ...createSession({ id: 'session_1', status: 'active' }, user), expire_at: 0, abandon_at: 0 }], + created_at: Date.now() - 1000, + updated_at: Date.now(), + } as any; + + // @ts-expect-error We cannot mess with the singleton when tests are running in parallel + const snapshot = new Client(clientJSON).__internal_toSnapshot(); + // @ts-expect-error We cannot mess with the singleton when tests are running in parallel + const [restored] = new Client(snapshot).sessions; + + expect(snapshot.sessions[0]).toMatchObject({ expire_at: 0, abandon_at: 0 }); + expect(restored.expireAt.getTime()).toBe(0); + expect(restored.abandonAt.getTime()).toBe(0); + expect(restored.status).toBe('active'); + }); + it('__internal_toSnapshot()', () => { const clientJSON = { object: 'client', diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index a549bbabfb1..b45c8052ac8 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -8,6 +8,7 @@ import { eventBus } from '../../events'; import { createFapiClient } from '../../fapiClient'; import { SessionTokenCache } from '../../tokenCache'; import { BaseResource, Organization, Session } from '../internal'; +import { SessionWithActivities } from '../SessionWithActivities'; const baseFapiClientOptions = { frontendApi: 'clerk.example.com', @@ -31,6 +32,32 @@ describe('Session', () => { vi.useRealTimers(); }); + it.each([ + { expire_at: 0, abandon_at: 0 }, + { expire_at: 2000000000000, abandon_at: 2000000000000 }, + { expire_at: 0, abandon_at: 2000000000000 }, + { expire_at: 2000000000000, abandon_at: 0 }, + ])('preserves session deadlines $expire_at and $abandon_at through snapshots', deadlines => { + const session = new Session({ + id: 'session_lifetime', + status: 'active', + ...deadlines, + public_user_data: { user_id: 'user_1' }, + } as SessionJSON); + expect(session.expireAt.getTime()).toBe(deadlines.expire_at); + expect(session.abandonAt.getTime()).toBe(deadlines.abandon_at); + + const snapshot = session.__internal_toSnapshot(); + expect(snapshot).toMatchObject(deadlines); + const restored = new Session(snapshot); + expect(restored.expireAt.getTime()).toBe(deadlines.expire_at); + expect(restored.abandonAt.getTime()).toBe(deadlines.abandon_at); + + const withActivities = new SessionWithActivities({ id: session.id, ...deadlines } as any, '/me/sessions'); + expect(withActivities.expireAt.getTime()).toBe(deadlines.expire_at); + expect(withActivities.abandonAt.getTime()).toBe(deadlines.abandon_at); + }); + describe('getToken()', () => { let dispatchSpy; diff --git a/packages/clerk-js/src/utils/__tests__/date.test.ts b/packages/clerk-js/src/utils/__tests__/date.test.ts index 3c1ff3a3ca7..a2311dbe636 100644 --- a/packages/clerk-js/src/utils/__tests__/date.test.ts +++ b/packages/clerk-js/src/utils/__tests__/date.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { unixEpochToDate } from '../date'; +import { unixEpochToDate, unixEpochToSessionDeadline } from '../date'; describe('date utilities', () => { beforeAll(() => { @@ -28,5 +28,24 @@ describe('date utilities', () => { const date = unixEpochToDate('a very strange value' as any); expect(date.getTime()).toBe(Date.now()); }); + + it('fallbacks to current date for a zero epoch', function () { + expect(unixEpochToDate(0).getTime()).toBe(Date.now()); + }); + }); + + describe('unixEpochToSessionDeadline', () => { + it('preserves an unset session deadline', () => { + expect(unixEpochToSessionDeadline(0).getTime()).toBe(0); + }); + + it('converts a finite session deadline to Date', () => { + expect(unixEpochToSessionDeadline(2000000000000).getTime()).toBe(2000000000000); + }); + + it('fallbacks to current date for undefined and invalid values', () => { + expect(unixEpochToSessionDeadline(undefined).getTime()).toBe(Date.now()); + expect(unixEpochToSessionDeadline('a very strange value' as any).getTime()).toBe(Date.now()); + }); }); }); diff --git a/packages/clerk-js/src/utils/date.ts b/packages/clerk-js/src/utils/date.ts index 9541ca70bab..3b0233e7d03 100644 --- a/packages/clerk-js/src/utils/date.ts +++ b/packages/clerk-js/src/utils/date.ts @@ -6,3 +6,8 @@ export function unixEpochToDate(epochInSeconds?: number): Date { const date = new Date(epochInSeconds || new Date()); return isValidDate(date) ? date : new Date(); } + +// Session deadlines use Unix epoch zero for "no deadline", so 0 must not fall back to the current time. +export function unixEpochToSessionDeadline(epochInMilliseconds?: number): Date { + return epochInMilliseconds === 0 ? new Date(0) : unixEpochToDate(epochInMilliseconds); +} From bb9e5818bc9a5b19786c4a453665fe91258f434c Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 21 Oct 2026 17:57:21 -0700 Subject: [PATCH 2/5] feat(clerk-js,shared): add hasMaximumLifetime to Core 2 session resources Session and SessionWithActivities expose hasMaximumLifetime, false when expireAt is Unix epoch zero. The SessionResource and SessionWithActivitiesResource types gain the optional property with the same documentation as Core 3. Bump clerk-js, shared and types as minor, and drop the comment above unixEpochToSessionDeadline. Part of MOBILE-517 --- .changeset/native-session-lifetime-core-2.md | 6 ++++-- packages/clerk-js/src/core/resources/Session.ts | 4 ++++ .../clerk-js/src/core/resources/SessionWithActivities.ts | 4 ++++ .../clerk-js/src/core/resources/__tests__/Client.test.ts | 1 + .../clerk-js/src/core/resources/__tests__/Session.test.ts | 3 +++ packages/clerk-js/src/utils/date.ts | 1 - packages/shared/src/types/session.ts | 8 ++++++++ 7 files changed, 24 insertions(+), 3 deletions(-) diff --git a/.changeset/native-session-lifetime-core-2.md b/.changeset/native-session-lifetime-core-2.md index e5265cb1b36..72d60f7b43a 100644 --- a/.changeset/native-session-lifetime-core-2.md +++ b/.changeset/native-session-lifetime-core-2.md @@ -1,5 +1,7 @@ --- -'@clerk/clerk-js': patch +'@clerk/clerk-js': minor +'@clerk/shared': minor +'@clerk/types': minor --- -Keep a session's `expireAt` and `abandonAt` at Unix epoch zero when the session has no maximum lifetime, instead of replacing them with the current time. Sessions restored from a client snapshot keep these deadlines too. +Add `hasMaximumLifetime` to `SessionResource` and `SessionWithActivitiesResource`. It is `false` for sessions without a maximum lifetime, whose `expireAt` and `abandonAt` now stay at Unix epoch zero instead of being replaced with the current time, including when a cached client is restored. diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index dd61d292aca..892aca7c23d 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -67,6 +67,10 @@ export class Session extends BaseResource implements SessionResource { return !!resource && resource instanceof Session; } + get hasMaximumLifetime(): boolean { + return this.expireAt.getTime() > 0; + } + constructor(data: SessionJSON | SessionJSONSnapshot) { super(); diff --git a/packages/clerk-js/src/core/resources/SessionWithActivities.ts b/packages/clerk-js/src/core/resources/SessionWithActivities.ts index edafb16f4f0..4cd9a9107ad 100644 --- a/packages/clerk-js/src/core/resources/SessionWithActivities.ts +++ b/packages/clerk-js/src/core/resources/SessionWithActivities.ts @@ -30,6 +30,10 @@ export class SessionWithActivities extends BaseResource implements SessionWithAc latestActivity!: SessionActivity; actor!: ActClaim | null; + get hasMaximumLifetime(): boolean { + return this.expireAt.getTime() > 0; + } + constructor(data: SessionWithActivitiesJSON, pathRoot: string) { super(); this.pathRoot = pathRoot; diff --git a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts index 1f86440b938..5b2096f359c 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts @@ -326,6 +326,7 @@ describe('Client Singleton', () => { expect(snapshot.sessions[0]).toMatchObject({ expire_at: 0, abandon_at: 0 }); expect(restored.expireAt.getTime()).toBe(0); expect(restored.abandonAt.getTime()).toBe(0); + expect(restored.hasMaximumLifetime).toBe(false); expect(restored.status).toBe('active'); }); diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index b45c8052ac8..e7cdd0abbba 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -46,16 +46,19 @@ describe('Session', () => { } as SessionJSON); expect(session.expireAt.getTime()).toBe(deadlines.expire_at); expect(session.abandonAt.getTime()).toBe(deadlines.abandon_at); + expect(session.hasMaximumLifetime).toBe(deadlines.expire_at > 0); const snapshot = session.__internal_toSnapshot(); expect(snapshot).toMatchObject(deadlines); const restored = new Session(snapshot); expect(restored.expireAt.getTime()).toBe(deadlines.expire_at); expect(restored.abandonAt.getTime()).toBe(deadlines.abandon_at); + expect(restored.hasMaximumLifetime).toBe(deadlines.expire_at > 0); const withActivities = new SessionWithActivities({ id: session.id, ...deadlines } as any, '/me/sessions'); expect(withActivities.expireAt.getTime()).toBe(deadlines.expire_at); expect(withActivities.abandonAt.getTime()).toBe(deadlines.abandon_at); + expect(withActivities.hasMaximumLifetime).toBe(deadlines.expire_at > 0); }); describe('getToken()', () => { diff --git a/packages/clerk-js/src/utils/date.ts b/packages/clerk-js/src/utils/date.ts index 3b0233e7d03..db088e517e1 100644 --- a/packages/clerk-js/src/utils/date.ts +++ b/packages/clerk-js/src/utils/date.ts @@ -7,7 +7,6 @@ export function unixEpochToDate(epochInSeconds?: number): Date { return isValidDate(date) ? date : new Date(); } -// Session deadlines use Unix epoch zero for "no deadline", so 0 must not fall back to the current time. export function unixEpochToSessionDeadline(epochInMilliseconds?: number): Date { return epochInMilliseconds === 0 ? new Date(0) : unixEpochToDate(epochInMilliseconds); } diff --git a/packages/shared/src/types/session.ts b/packages/shared/src/types/session.ts index 9d58c491e08..ebb07764a0b 100644 --- a/packages/shared/src/types/session.ts +++ b/packages/shared/src/types/session.ts @@ -217,6 +217,10 @@ export interface SessionResource extends ClerkResource { */ status: SessionStatus; expireAt: Date; + /** + * Whether the session has a maximum lifetime. `false` when `expireAt` is Unix epoch zero. A session without a maximum lifetime can still end through sign-out or revocation, so use `status` to check whether it is active. `undefined` in ClerkJS versions that predate this property. + */ + readonly hasMaximumLifetime?: boolean; abandonAt: Date; /** * An array where each item represents the number of minutes since the last verification of a first or second factor: `[firstFactorAge, secondFactorAge]`. @@ -291,6 +295,10 @@ export interface SessionWithActivitiesResource extends ClerkResource { id: string; status: string; expireAt: Date; + /** + * Whether the session has a maximum lifetime. `false` when `expireAt` is Unix epoch zero. `undefined` in ClerkJS versions that predate this property. + */ + readonly hasMaximumLifetime?: boolean; abandonAt: Date; lastActiveAt: Date; latestActivity: SessionActivity; From ebd67318dc26447276bdbdd9511d38e7c97be68b Mon Sep 17 00:00:00 2001 From: sam Date: Wed, 21 Oct 2026 21:56:50 -0700 Subject: [PATCH 3/5] ci: run checks now that the pull request is ready for review From c393468f08f54e5484dda90c7eebd90bd921c365 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 22 Oct 2026 11:15:50 -0700 Subject: [PATCH 4/5] fix(clerk-js,shared): drop hasMaximumLifetime from Core 2 session resources Remove hasMaximumLifetime from Session, SessionWithActivities and their resource types. Session expireAt and abandonAt still stay at Unix epoch zero for sessions without a maximum lifetime, and the changeset is now a clerk-js patch for that fix. Part of MOBILE-517 --- .changeset/native-session-lifetime-core-2.md | 6 ++---- packages/clerk-js/src/core/resources/Session.ts | 4 ---- .../clerk-js/src/core/resources/SessionWithActivities.ts | 4 ---- .../clerk-js/src/core/resources/__tests__/Client.test.ts | 1 - .../clerk-js/src/core/resources/__tests__/Session.test.ts | 3 --- packages/shared/src/types/session.ts | 8 -------- 6 files changed, 2 insertions(+), 24 deletions(-) diff --git a/.changeset/native-session-lifetime-core-2.md b/.changeset/native-session-lifetime-core-2.md index 72d60f7b43a..b294460a7f6 100644 --- a/.changeset/native-session-lifetime-core-2.md +++ b/.changeset/native-session-lifetime-core-2.md @@ -1,7 +1,5 @@ --- -'@clerk/clerk-js': minor -'@clerk/shared': minor -'@clerk/types': minor +'@clerk/clerk-js': patch --- -Add `hasMaximumLifetime` to `SessionResource` and `SessionWithActivitiesResource`. It is `false` for sessions without a maximum lifetime, whose `expireAt` and `abandonAt` now stay at Unix epoch zero instead of being replaced with the current time, including when a cached client is restored. +Keep a session's `expireAt` and `abandonAt` at Unix epoch zero when the session has no maximum lifetime, instead of falling back to the current time. diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index 892aca7c23d..dd61d292aca 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -67,10 +67,6 @@ export class Session extends BaseResource implements SessionResource { return !!resource && resource instanceof Session; } - get hasMaximumLifetime(): boolean { - return this.expireAt.getTime() > 0; - } - constructor(data: SessionJSON | SessionJSONSnapshot) { super(); diff --git a/packages/clerk-js/src/core/resources/SessionWithActivities.ts b/packages/clerk-js/src/core/resources/SessionWithActivities.ts index 4cd9a9107ad..edafb16f4f0 100644 --- a/packages/clerk-js/src/core/resources/SessionWithActivities.ts +++ b/packages/clerk-js/src/core/resources/SessionWithActivities.ts @@ -30,10 +30,6 @@ export class SessionWithActivities extends BaseResource implements SessionWithAc latestActivity!: SessionActivity; actor!: ActClaim | null; - get hasMaximumLifetime(): boolean { - return this.expireAt.getTime() > 0; - } - constructor(data: SessionWithActivitiesJSON, pathRoot: string) { super(); this.pathRoot = pathRoot; diff --git a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts index 5b2096f359c..1f86440b938 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Client.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Client.test.ts @@ -326,7 +326,6 @@ describe('Client Singleton', () => { expect(snapshot.sessions[0]).toMatchObject({ expire_at: 0, abandon_at: 0 }); expect(restored.expireAt.getTime()).toBe(0); expect(restored.abandonAt.getTime()).toBe(0); - expect(restored.hasMaximumLifetime).toBe(false); expect(restored.status).toBe('active'); }); diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index e7cdd0abbba..b45c8052ac8 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -46,19 +46,16 @@ describe('Session', () => { } as SessionJSON); expect(session.expireAt.getTime()).toBe(deadlines.expire_at); expect(session.abandonAt.getTime()).toBe(deadlines.abandon_at); - expect(session.hasMaximumLifetime).toBe(deadlines.expire_at > 0); const snapshot = session.__internal_toSnapshot(); expect(snapshot).toMatchObject(deadlines); const restored = new Session(snapshot); expect(restored.expireAt.getTime()).toBe(deadlines.expire_at); expect(restored.abandonAt.getTime()).toBe(deadlines.abandon_at); - expect(restored.hasMaximumLifetime).toBe(deadlines.expire_at > 0); const withActivities = new SessionWithActivities({ id: session.id, ...deadlines } as any, '/me/sessions'); expect(withActivities.expireAt.getTime()).toBe(deadlines.expire_at); expect(withActivities.abandonAt.getTime()).toBe(deadlines.abandon_at); - expect(withActivities.hasMaximumLifetime).toBe(deadlines.expire_at > 0); }); describe('getToken()', () => { diff --git a/packages/shared/src/types/session.ts b/packages/shared/src/types/session.ts index ebb07764a0b..9d58c491e08 100644 --- a/packages/shared/src/types/session.ts +++ b/packages/shared/src/types/session.ts @@ -217,10 +217,6 @@ export interface SessionResource extends ClerkResource { */ status: SessionStatus; expireAt: Date; - /** - * Whether the session has a maximum lifetime. `false` when `expireAt` is Unix epoch zero. A session without a maximum lifetime can still end through sign-out or revocation, so use `status` to check whether it is active. `undefined` in ClerkJS versions that predate this property. - */ - readonly hasMaximumLifetime?: boolean; abandonAt: Date; /** * An array where each item represents the number of minutes since the last verification of a first or second factor: `[firstFactorAge, secondFactorAge]`. @@ -295,10 +291,6 @@ export interface SessionWithActivitiesResource extends ClerkResource { id: string; status: string; expireAt: Date; - /** - * Whether the session has a maximum lifetime. `false` when `expireAt` is Unix epoch zero. `undefined` in ClerkJS versions that predate this property. - */ - readonly hasMaximumLifetime?: boolean; abandonAt: Date; lastActiveAt: Date; latestActivity: SessionActivity; From 3863df0e5a48e1166fad6782fc8692922278bac9 Mon Sep 17 00:00:00 2001 From: sam Date: Thu, 22 Oct 2026 15:27:36 -0700 Subject: [PATCH 5/5] refactor(clerk-js): match the Core 3 session deadline helper Rename unixEpochToSessionDeadline to unixEpochToDeadline and use the same helper, tests and session fallback test as Core 3 main, so later fixes cherry-pick cleanly. Part of MOBILE-517 --- .../clerk-js/src/core/resources/Session.ts | 6 ++--- .../core/resources/SessionWithActivities.ts | 6 ++--- .../core/resources/__tests__/Session.test.ts | 17 +++++++++++++ .../clerk-js/src/utils/__tests__/date.test.ts | 25 +++++++++---------- packages/clerk-js/src/utils/date.ts | 4 +-- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/packages/clerk-js/src/core/resources/Session.ts b/packages/clerk-js/src/core/resources/Session.ts index dd61d292aca..454815b8c0f 100644 --- a/packages/clerk-js/src/core/resources/Session.ts +++ b/packages/clerk-js/src/core/resources/Session.ts @@ -28,7 +28,7 @@ import type { } from '@clerk/shared/types'; import { isWebAuthnSupported as isWebAuthnSupportedOnWindow } from '@clerk/shared/webauthn'; -import { unixEpochToDate, unixEpochToSessionDeadline } from '@/utils/date'; +import { unixEpochToDate, unixEpochToDeadline } from '@/utils/date'; import { debugLogger } from '@/utils/debug'; import { convertJSONToPublicKeyRequestOptions, @@ -309,8 +309,8 @@ export class Session extends BaseResource implements SessionResource { this.id = data.id; this.status = data.status; - this.expireAt = unixEpochToSessionDeadline(data.expire_at); - this.abandonAt = unixEpochToSessionDeadline(data.abandon_at); + this.expireAt = unixEpochToDeadline(data.expire_at); + this.abandonAt = unixEpochToDeadline(data.abandon_at); this.factorVerificationAge = data.factor_verification_age; this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined); this.lastActiveOrganizationId = data.last_active_organization_id; diff --git a/packages/clerk-js/src/core/resources/SessionWithActivities.ts b/packages/clerk-js/src/core/resources/SessionWithActivities.ts index edafb16f4f0..d925f0fc7e2 100644 --- a/packages/clerk-js/src/core/resources/SessionWithActivities.ts +++ b/packages/clerk-js/src/core/resources/SessionWithActivities.ts @@ -6,7 +6,7 @@ import type { SessionWithActivitiesResource, } from '@clerk/shared/types'; -import { unixEpochToDate, unixEpochToSessionDeadline } from '../../utils/date'; +import { unixEpochToDate, unixEpochToDeadline } from '../../utils/date'; import { BaseResource } from './internal'; const mapSessionActivityJSONToSessionActivity = (data: SessionActivityJSON): SessionActivity => ({ @@ -65,8 +65,8 @@ export class SessionWithActivities extends BaseResource implements SessionWithAc this.id = data.id; this.status = data.status; - this.expireAt = unixEpochToSessionDeadline(data.expire_at); - this.abandonAt = unixEpochToSessionDeadline(data.abandon_at); + this.expireAt = unixEpochToDeadline(data.expire_at); + this.abandonAt = unixEpochToDeadline(data.abandon_at); this.lastActiveAt = unixEpochToDate(data.last_active_at || undefined); this.latestActivity = mapSessionActivityJSONToSessionActivity(data.latest_activity ?? ({} as SessionActivityJSON)); this.actor = data.actor; diff --git a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts index b45c8052ac8..292234017a4 100644 --- a/packages/clerk-js/src/core/resources/__tests__/Session.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/Session.test.ts @@ -58,6 +58,23 @@ describe('Session', () => { expect(withActivities.abandonAt.getTime()).toBe(deadlines.abandon_at); }); + it('keeps zero only for session deadlines', () => { + const session = new Session({ + id: 'session_lifetime', + status: 'active', + expire_at: 0, + abandon_at: 0, + created_at: 0, + updated_at: 0, + public_user_data: { user_id: 'user_1' }, + } as SessionJSON); + + expect(session.expireAt.getTime()).toBe(0); + expect(session.abandonAt.getTime()).toBe(0); + expect(session.createdAt.getTime()).toBe(Date.now()); + expect(session.updatedAt.getTime()).toBe(Date.now()); + }); + describe('getToken()', () => { let dispatchSpy; diff --git a/packages/clerk-js/src/utils/__tests__/date.test.ts b/packages/clerk-js/src/utils/__tests__/date.test.ts index a2311dbe636..0b8a303999e 100644 --- a/packages/clerk-js/src/utils/__tests__/date.test.ts +++ b/packages/clerk-js/src/utils/__tests__/date.test.ts @@ -1,6 +1,6 @@ import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'; -import { unixEpochToDate, unixEpochToSessionDeadline } from '../date'; +import { unixEpochToDate, unixEpochToDeadline } from '../date'; describe('date utilities', () => { beforeAll(() => { @@ -13,6 +13,10 @@ describe('date utilities', () => { }); describe('unixEpochToDate', () => { + it('fallbacks to current date for zero', function () { + expect(unixEpochToDate(0).getTime()).toBe(Date.now()); + }); + it('converts a unix epoch to Date', function () { const epoch = 1622575087; const date = unixEpochToDate(epoch); @@ -28,24 +32,19 @@ describe('date utilities', () => { const date = unixEpochToDate('a very strange value' as any); expect(date.getTime()).toBe(Date.now()); }); - - it('fallbacks to current date for a zero epoch', function () { - expect(unixEpochToDate(0).getTime()).toBe(Date.now()); - }); }); - describe('unixEpochToSessionDeadline', () => { - it('preserves an unset session deadline', () => { - expect(unixEpochToSessionDeadline(0).getTime()).toBe(0); + describe('unixEpochToDeadline', () => { + it('keeps zero as Unix epoch zero', () => { + expect(unixEpochToDeadline(0).getTime()).toBe(0); }); - it('converts a finite session deadline to Date', () => { - expect(unixEpochToSessionDeadline(2000000000000).getTime()).toBe(2000000000000); + it('converts a unix epoch to Date', () => { + expect(unixEpochToDeadline(1622575087).getTime()).toBe(1622575087); }); - it('fallbacks to current date for undefined and invalid values', () => { - expect(unixEpochToSessionDeadline(undefined).getTime()).toBe(Date.now()); - expect(unixEpochToSessionDeadline('a very strange value' as any).getTime()).toBe(Date.now()); + it('fallbacks to current date for undefined values', () => { + expect(unixEpochToDeadline(undefined).getTime()).toBe(Date.now()); }); }); }); diff --git a/packages/clerk-js/src/utils/date.ts b/packages/clerk-js/src/utils/date.ts index db088e517e1..f785cbe118e 100644 --- a/packages/clerk-js/src/utils/date.ts +++ b/packages/clerk-js/src/utils/date.ts @@ -7,6 +7,6 @@ export function unixEpochToDate(epochInSeconds?: number): Date { return isValidDate(date) ? date : new Date(); } -export function unixEpochToSessionDeadline(epochInMilliseconds?: number): Date { - return epochInMilliseconds === 0 ? new Date(0) : unixEpochToDate(epochInMilliseconds); +export function unixEpochToDeadline(epoch?: number): Date { + return epoch === 0 ? new Date(0) : unixEpochToDate(epoch); }