@@ -686,7 +686,7 @@ describe('ProtectSession deadlines', () => {
686686
687687 const startedAt = Date . now ( ) ;
688688 await expect ( created ?. getRequestParams ( ) ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
689- // Misread, this would fall back to the 5s default and be two orders of magnitude out.
689+ // Misread, this would fall back to the 10s default and be two orders of magnitude out.
690690 expect ( Date . now ( ) - startedAt ) . toBeLessThan ( 1_000 ) ;
691691 } ) ;
692692
@@ -733,11 +733,204 @@ describe('ProtectSession deadlines', () => {
733733 // `tokenTimeoutMs` is server config with no upper bound of its own, so the ceiling is ours.
734734 expect ( clampTimeout ( 120_000 ) ) . toBe ( 10_000 ) ;
735735 expect ( clampTimeout ( 1_500 ) ) . toBe ( 1_500 ) ;
736- expect ( clampTimeout ( undefined ) ) . toBe ( 5_000 ) ;
737- expect ( clampTimeout ( 0 ) ) . toBe ( 5_000 ) ;
738- expect ( clampTimeout ( - 1 ) ) . toBe ( 5_000 ) ;
739- expect ( clampTimeout ( 'soon' ) ) . toBe ( 5_000 ) ;
740- expect ( clampTimeout ( Number . POSITIVE_INFINITY ) ) . toBe ( 5_000 ) ;
736+ expect ( clampTimeout ( undefined ) ) . toBe ( 10_000 ) ;
737+ expect ( clampTimeout ( 0 ) ) . toBe ( 10_000 ) ;
738+ expect ( clampTimeout ( - 1 ) ) . toBe ( 10_000 ) ;
739+ expect ( clampTimeout ( 'soon' ) ) . toBe ( 10_000 ) ;
740+ expect ( clampTimeout ( Number . POSITIVE_INFINITY ) ) . toBe ( 10_000 ) ;
741+ } ) ;
742+ } ) ;
743+
744+ describe ( 'ProtectSession default deadline' , ( ) => {
745+ const defaultLoader = ( ) => loader ( { token_timeout_ms : undefined } ) ;
746+
747+ const track = ( params : Promise < unknown > | undefined ) => {
748+ const state = { settled : false } ;
749+ void params ?. then ( ( ) => {
750+ state . settled = true ;
751+ } ) ;
752+ return state ;
753+ } ;
754+
755+ beforeEach ( ( ) => {
756+ vi . useFakeTimers ( ) ;
757+ } ) ;
758+
759+ afterEach ( ( ) => {
760+ vi . useRealTimers ( ) ;
761+ delete ( navigator as unknown as Record < string , unknown > ) . locks ;
762+ } ) ;
763+
764+ it ( 'accepts a token that arrives after 7s' , async ( ) => {
765+ const { session : created , elements } = session ( [ defaultLoader ( ) ] ) ;
766+ created ?. start ( ) ;
767+ const params = created ?. getRequestParams ( ) ;
768+
769+ await vi . advanceTimersByTimeAsync ( 7_000 ) ;
770+ serveInline ( elements [ 0 ] , { cid : created ?. placeholders ( ) . cid } ) ;
771+
772+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'ok' } ) ;
773+ } ) ;
774+
775+ it ( 'gives up at 10s when the loader never loads' , async ( ) => {
776+ const { session : created } = session ( [ defaultLoader ( ) ] ) ;
777+ created ?. start ( ) ;
778+ const params = created ?. getRequestParams ( ) ;
779+ const state = track ( params ) ;
780+
781+ await vi . advanceTimersByTimeAsync ( 9_999 ) ;
782+ expect ( state . settled ) . toBe ( false ) ;
783+ await vi . advanceTimersByTimeAsync ( 1 ) ;
784+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
785+ } ) ;
786+
787+ it ( 'holds a sign-in made mid-acquisition only for what is left of it' , async ( ) => {
788+ const { session : created } = session ( [ defaultLoader ( ) ] ) ;
789+ created ?. start ( ) ;
790+ await vi . advanceTimersByTimeAsync ( 6_000 ) ;
791+
792+ const params = created ?. getRequestParams ( ) ;
793+ const state = track ( params ) ;
794+
795+ await vi . advanceTimersByTimeAsync ( 3_999 ) ;
796+ expect ( state . settled ) . toBe ( false ) ;
797+ await vi . advanceTimersByTimeAsync ( 1 ) ;
798+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
799+ } ) ;
800+
801+ it ( 'never holds one sign-in past the deadline when the lock is granted late' , async ( ) => {
802+ Object . defineProperty ( navigator , 'locks' , {
803+ value : {
804+ request : vi . fn ( ( _key : string , _options : unknown , callback : ( ) => Promise < unknown > ) =>
805+ new Promise ( resolve => setTimeout ( resolve , 4_900 ) ) . then ( ( ) => callback ( ) ) ,
806+ ) ,
807+ } ,
808+ configurable : true ,
809+ } ) ;
810+
811+ const { session : created } = session ( [ defaultLoader ( ) ] ) ;
812+ created ?. start ( ) ;
813+ const params = created ?. getRequestParams ( ) ;
814+ const state = track ( params ) ;
815+
816+ await vi . advanceTimersByTimeAsync ( 9_999 ) ;
817+ expect ( state . settled ) . toBe ( false ) ;
818+ await vi . advanceTimersByTimeAsync ( 1 ) ;
819+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
820+ } ) ;
821+
822+ it ( 'keeps waiting for a leader tab after SafeLock gives up, until its own deadline' , async ( ) => {
823+ Object . defineProperty ( navigator , 'locks' , {
824+ value : {
825+ request : vi . fn (
826+ ( _key : string , options : { signal : AbortSignal } ) =>
827+ new Promise ( ( _resolve , reject ) => {
828+ options . signal . addEventListener ( 'abort' , ( ) => reject ( new DOMException ( 'aborted' , 'AbortError' ) ) ) ;
829+ } ) ,
830+ ) ,
831+ } ,
832+ configurable : true ,
833+ } ) ;
834+
835+ const { session : created , elements } = session ( [ defaultLoader ( ) ] ) ;
836+ created ?. start ( ) ;
837+ const params = created ?. getRequestParams ( ) ;
838+
839+ await vi . advanceTimersByTimeAsync ( 7_000 ) ;
840+ localStorage . setItem ( '__clerk_protect_st' , storedEntry ( { token : 'v1.leader.mac' , rid : 'c' . repeat ( 26 ) } ) ) ;
841+ await vi . advanceTimersByTimeAsync ( 100 ) ;
842+
843+ await expect ( params ) . resolves . toMatchObject ( {
844+ __clerk_protect_token : 'v1.leader.mac' ,
845+ __clerk_protect_status : 'ok' ,
846+ } ) ;
847+ expect ( elements ) . toHaveLength ( 0 ) ;
848+ } ) ;
849+
850+ it ( 'answers at once when the browser refuses the cross-tab lock' , async ( ) => {
851+ Object . defineProperty ( navigator , 'locks' , {
852+ value : { request : vi . fn ( ( ) => Promise . reject ( new DOMException ( 'denied' , 'SecurityError' ) ) ) } ,
853+ configurable : true ,
854+ } ) ;
855+
856+ const { session : created , elements } = session ( [ defaultLoader ( ) ] ) ;
857+ created ?. start ( ) ;
858+ const params = created ?. getRequestParams ( ) ;
859+ const state = track ( params ) ;
860+
861+ await vi . advanceTimersByTimeAsync ( 50 ) ;
862+ expect ( state . settled ) . toBe ( true ) ;
863+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
864+ expect ( elements ) . toHaveLength ( 0 ) ;
865+ } ) ;
866+
867+ it ( 'stops at its own deadline when that comes before SafeLock gives up' , async ( ) => {
868+ Object . defineProperty ( navigator , 'locks' , {
869+ value : {
870+ request : vi . fn (
871+ ( _key : string , options : { signal : AbortSignal } ) =>
872+ new Promise ( ( _resolve , reject ) => {
873+ options . signal . addEventListener ( 'abort' , ( ) => reject ( new DOMException ( 'aborted' , 'AbortError' ) ) ) ;
874+ } ) ,
875+ ) ,
876+ } ,
877+ configurable : true ,
878+ } ) ;
879+
880+ const { session : created , elements } = session ( [ loader ( { token_timeout_ms : 3_000 } ) ] ) ;
881+ created ?. start ( ) ;
882+ const params = created ?. getRequestParams ( ) ;
883+ const state = track ( params ) ;
884+
885+ await vi . advanceTimersByTimeAsync ( 2_999 ) ;
886+ expect ( state . settled ) . toBe ( false ) ;
887+ await vi . advanceTimersByTimeAsync ( 1 ) ;
888+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
889+
890+ localStorage . setItem ( '__clerk_protect_st' , storedEntry ( { token : 'v1.leader.mac' , rid : 'c' . repeat ( 26 ) } ) ) ;
891+ await vi . advanceTimersByTimeAsync ( 2_000 ) ;
892+ expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
893+ await expect ( created ?. getRequestParams ( ) ) . resolves . toMatchObject ( { __clerk_protect_token : 'v1.leader.mac' } ) ;
894+ expect ( elements ) . toHaveLength ( 0 ) ;
895+ } ) ;
896+
897+ it . each ( [
898+ [ 2_000 , - 3_600_000 ] ,
899+ [ 6_000 , - 3_600_000 ] ,
900+ [ 2_000 , 3_600_000 ] ,
901+ [ 6_000 , 3_600_000 ] ,
902+ ] ) ( 'settles follower polling after a clock change at %s ms by %s ms' , async ( changeAt , correction ) => {
903+ let available = false ;
904+ const request = vi . fn ( ( _key : string , options : { signal : AbortSignal } , callback : ( ) => Promise < unknown > ) => {
905+ if ( available ) {
906+ return callback ( ) ;
907+ }
908+ return new Promise ( ( _resolve , reject ) => {
909+ options . signal . addEventListener ( 'abort' , ( ) => reject ( new DOMException ( 'aborted' , 'AbortError' ) ) ) ;
910+ } ) ;
911+ } ) ;
912+ Object . defineProperty ( navigator , 'locks' , { configurable : true , value : { request } } ) ;
913+ const { session : created , elements } = session ( [ defaultLoader ( ) ] ) ;
914+ created ?. start ( ) ;
915+ const params = created ?. getRequestParams ( ) ;
916+ const state = track ( params ) ;
917+
918+ await vi . advanceTimersByTimeAsync ( changeAt ) ;
919+ vi . setSystemTime ( Date . now ( ) + correction ) ;
920+ await vi . advanceTimersByTimeAsync ( 9_999 - changeAt ) ;
921+ expect ( state . settled ) . toBe ( false ) ;
922+ await vi . advanceTimersByTimeAsync ( 1 ) ;
923+ await expect ( params ) . resolves . toMatchObject ( { __clerk_protect_status : 'timeout' } ) ;
924+ expect ( vi . getTimerCount ( ) ) . toBe ( 0 ) ;
925+
926+ available = true ;
927+ await vi . advanceTimersByTimeAsync ( 30_000 ) ;
928+ const retry = created ?. getRequestParams ( ) ;
929+ await vi . advanceTimersByTimeAsync ( 0 ) ;
930+ expect ( request ) . toHaveBeenCalledTimes ( 2 ) ;
931+ expect ( elements ) . toHaveLength ( 1 ) ;
932+ serveInline ( elements [ 0 ] , { cid : created ?. placeholders ( ) . cid } ) ;
933+ await expect ( retry ) . resolves . toMatchObject ( { __clerk_protect_status : 'ok' } ) ;
741934 } ) ;
742935} ) ;
743936
0 commit comments