Skip to content

Commit 57ef049

Browse files
committed
feat(clerk-js,types): Rename to SignInFuture, use resource-agnostic events
1 parent 45e3607 commit 57ef049

File tree

6 files changed

+150
-25
lines changed

6 files changed

+150
-25
lines changed
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
import { createEventBus } from '@clerk/shared/eventBus';
22
import type { TokenResource } from '@clerk/types';
33

4-
import type { SignIn } from './resources';
4+
import { BaseResource } from './resources/Base';
55

66
export const events = {
77
TokenUpdate: 'token:update',
88
UserSignOut: 'user:signOut',
99
EnvironmentUpdate: 'environment:update',
1010
SessionTokenResolved: 'session:tokenResolved',
11-
SignInUpdate: 'signin:update',
12-
SignInError: 'signin:error',
11+
ResourceUpdate: 'resource:update',
12+
ResourceError: 'resource:error',
1313
} as const;
1414

1515
type TokenUpdatePayload = { token: TokenResource | null };
16-
export type SignInUpdatePayload = { resource: SignIn };
16+
export type ResourceUpdatePayload = { resource: BaseResource };
17+
export type ResourceErrorPayload = { resource: BaseResource; error: unknown };
1718

1819
type InternalEvents = {
1920
[events.TokenUpdate]: TokenUpdatePayload;
2021
[events.UserSignOut]: null;
2122
[events.EnvironmentUpdate]: null;
2223
[events.SessionTokenResolved]: null;
23-
[events.SignInUpdate]: SignInUpdatePayload;
24-
[events.SignInError]: any;
24+
[events.ResourceUpdate]: ResourceUpdatePayload;
25+
[events.ResourceError]: ResourceErrorPayload;
2526
};
2627

2728
export const eventBus = createEventBus<InternalEvents>();

packages/clerk-js/src/core/resources/SignIn.ts

Lines changed: 113 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import type {
3030
SignInIdentifier,
3131
SignInJSON,
3232
SignInJSONSnapshot,
33-
SignInBetaResource,
33+
SignInFutureResource,
3434
SignInResource,
3535
SignInSecondFactor,
3636
SignInStartEmailLinkFlowParams,
@@ -39,6 +39,7 @@ import type {
3939
Web3Provider,
4040
Web3SignatureConfig,
4141
Web3SignatureFactor,
42+
OAuthStrategy,
4243
} from '@clerk/types';
4344

4445
import {
@@ -84,7 +85,7 @@ export class SignIn extends BaseResource implements SignInResource {
8485
createdSessionId: string | null = null;
8586
userData: UserData = new UserData(null);
8687

87-
__internal_beta: SignInBeta | null = new SignInBeta(this);
88+
__internal_beta: SignInFuture | null = new SignInFuture(this);
8889
__internal_basePost;
8990

9091
constructor(data: SignInJSON | SignInJSONSnapshot | null = null) {
@@ -459,7 +460,7 @@ export class SignIn extends BaseResource implements SignInResource {
459460
this.userData = new UserData(data.user_data);
460461
}
461462

462-
eventBus.emit('signin:update', { resource: this });
463+
eventBus.emit('resource:update', { resource: this });
463464
return this;
464465
}
465466

@@ -480,22 +481,128 @@ export class SignIn extends BaseResource implements SignInResource {
480481
}
481482
}
482483

483-
class SignInBeta implements SignInBetaResource {
484+
class SignInFuture implements SignInFutureResource {
485+
emailCode = {
486+
sendCode: this.sendEmailCode.bind(this),
487+
verifyCode: this.verifyEmailCode.bind(this),
488+
};
489+
484490
constructor(readonly resource: SignIn) {}
485491

486492
get status() {
487493
return this.resource.status;
488494
}
489495

496+
async create(params: {
497+
identifier?: string;
498+
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
499+
redirectUrl?: string;
500+
actionCompleteRedirectUrl?: string;
501+
}): Promise<{ error: unknown }> {
502+
eventBus.emit('resource:error', { resource: this.resource, error: null });
503+
try {
504+
await this.resource.__internal_basePost({
505+
path: this.resource.pathRoot,
506+
body: params,
507+
});
508+
509+
return { error: null };
510+
} catch (err) {
511+
eventBus.emit('resource:error', { resource: this.resource, error: err });
512+
return { error: err };
513+
}
514+
}
515+
490516
async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
491-
eventBus.emit('signin:error', null);
517+
eventBus.emit('resource:error', { resource: this.resource, error: null });
492518
try {
493519
await this.resource.__internal_basePost({
494520
path: this.resource.pathRoot,
495521
body: { identifier, password },
496522
});
497523
} catch (err) {
498-
eventBus.emit('signin:error', err);
524+
eventBus.emit('resource:error', { resource: this.resource, error: err });
525+
return { error: err };
526+
}
527+
528+
return { error: null };
529+
}
530+
531+
async sendEmailCode({ email }: { email: string }): Promise<{ error: unknown }> {
532+
eventBus.emit('resource:error', { resource: this.resource, error: null });
533+
try {
534+
if (!this.resource.id) {
535+
await this.create({ identifier: email });
536+
}
537+
538+
const emailCodeFactor = this.resource.supportedFirstFactors?.find(f => f.strategy === 'email_code');
539+
540+
if (!emailCodeFactor) {
541+
throw new Error('Email code factor not found');
542+
}
543+
544+
const { emailAddressId } = emailCodeFactor;
545+
await this.resource.__internal_basePost({
546+
body: { emailAddressId, strategy: 'email_code' },
547+
action: 'prepare_first_factor',
548+
});
549+
} catch (err: unknown) {
550+
eventBus.emit('resource:error', { resource: this.resource, error: err });
551+
return { error: err };
552+
}
553+
554+
return { error: null };
555+
}
556+
557+
async verifyEmailCode({ code }: { code: string }): Promise<{ error: unknown }> {
558+
eventBus.emit('resource:error', { resource: this.resource, error: null });
559+
try {
560+
await this.resource.__internal_basePost({
561+
body: { code, strategy: 'email_code' },
562+
action: 'attempt_first_factor',
563+
});
564+
} catch (err: unknown) {
565+
eventBus.emit('resource:error', { resource: this.resource, error: err });
566+
return { error: err };
567+
}
568+
569+
return { error: null };
570+
}
571+
572+
async sso({
573+
flow = 'auto',
574+
strategy,
575+
redirectUrl,
576+
redirectUrlComplete,
577+
}: {
578+
flow?: 'auto' | 'modal';
579+
strategy: OAuthStrategy | 'saml' | 'enterprise_sso';
580+
redirectUrl: string;
581+
redirectUrlComplete: string;
582+
}): Promise<{ error: unknown }> {
583+
eventBus.emit('resource:error', { resource: this.resource, error: null });
584+
try {
585+
if (flow !== 'auto') {
586+
throw new Error('modal flow is not supported yet');
587+
}
588+
589+
const redirectUrlWithAuthToken = SignIn.clerk.buildUrlWithAuth(redirectUrl);
590+
591+
if (!this.resource.id) {
592+
await this.create({
593+
strategy,
594+
redirectUrl: redirectUrlWithAuthToken,
595+
actionCompleteRedirectUrl: redirectUrlComplete,
596+
});
597+
}
598+
599+
const { status, externalVerificationRedirectURL } = this.resource.firstFactorVerification;
600+
601+
if (status === 'unverified' && externalVerificationRedirectURL) {
602+
windowNavigate(externalVerificationRedirectURL);
603+
}
604+
} catch (err: unknown) {
605+
eventBus.emit('resource:error', { resource: this.resource, error: err });
499606
return { error: err };
500607
}
501608

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { State as StateInterface } from '@clerk/types';
22
import { computed, effect } from 'alien-signals';
33

4-
import { eventBus, type SignInUpdatePayload } from './events';
4+
import { eventBus } from './events';
55
import { signInComputedSignal, signInErrorSignal, signInSignal } from './signals';
6+
import { BaseResource } from './resources/Base';
7+
import { SignIn } from './resources/SignIn';
68

79
export class State implements StateInterface {
810
signInResourceSignal = signInSignal;
@@ -13,15 +15,19 @@ export class State implements StateInterface {
1315
__internal_computed = computed;
1416

1517
constructor() {
16-
eventBus.on('signin:update', this.onSignInUpdated);
17-
eventBus.on('signin:error', this.onSignInError);
18+
eventBus.on('resource:update', this.onResourceUpdated);
19+
eventBus.on('resource:error', this.onResourceError);
1820
}
1921

20-
private onSignInUpdated = (payload: SignInUpdatePayload) => {
21-
this.signInResourceSignal({ resource: payload.resource });
22+
private onResourceError = (payload: { resource: BaseResource; error: unknown }) => {
23+
if (payload.resource instanceof SignIn) {
24+
this.signInErrorSignal({ errors: payload.error });
25+
}
2226
};
2327

24-
private onSignInError = (error: unknown) => {
25-
this.signInErrorSignal({ errors: error });
28+
private onResourceUpdated = (payload: { resource: BaseResource }) => {
29+
if (payload.resource instanceof SignIn) {
30+
this.signInResourceSignal({ resource: payload.resource });
31+
}
2632
};
2733
}

packages/react/src/hooks/useClerkSignal.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { SignInBetaResource } from '@clerk/types';
1+
import type { SignInFutureResource } from '@clerk/types';
22
import { useCallback, useSyncExternalStore } from 'react';
33

44
import { useIsomorphicClerkContext } from '../contexts/IsomorphicClerkContext';
55
import { useAssertWrappedByClerkProvider } from './useAssertWrappedByClerkProvider';
66

7-
function useClerkSignal(signal: 'signIn'): { errors: unknown; signIn: SignInBetaResource | null } | null {
7+
function useClerkSignal(signal: 'signIn'): { errors: unknown; signIn: SignInFutureResource | null } | null {
88
useAssertWrappedByClerkProvider('useSignInSignal');
99

1010
const clerk = useIsomorphicClerkContext();

packages/types/src/signIn.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,20 @@ export interface SignInResource extends ClerkResource {
125125
__internal_toSnapshot: () => SignInJSONSnapshot;
126126
}
127127

128-
export interface SignInBetaResource {
128+
export interface SignInFutureResource {
129129
status: SignInStatus | null;
130+
create: (params: { identifier: string }) => Promise<{ error: unknown }>;
130131
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
132+
emailCode: {
133+
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
134+
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
135+
};
136+
sso: (params: {
137+
flow?: 'auto' | 'modal';
138+
strategy: OAuthStrategy | 'saml' | 'enterprise_sso';
139+
redirectUrl: string;
140+
redirectUrlComplete: string;
141+
}) => Promise<{ error: unknown }>;
131142
}
132143

133144
export type SignInStatus =

packages/types/src/state.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import type { SignInBetaResource } from './signIn';
1+
import type { SignInFutureResource } from './signIn';
22

33
export interface State {
44
signInSignal: {
55
(): {
66
errors: unknown;
7-
signIn: SignInBetaResource | null;
7+
signIn: SignInFutureResource | null;
88
};
9-
(value: { errors: unknown; signIn: SignInBetaResource | null }): void;
9+
(value: { errors: unknown; signIn: SignInFutureResource | null }): void;
1010
};
1111
__internal_effect: (callback: () => void) => () => void;
1212
__internal_computed: <T>(getter: (previousValue?: T) => T) => () => T;

0 commit comments

Comments
 (0)