Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/young-cats-retire.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
---
'@clerk/clerk-js': minor
'@clerk/shared': minor
'@clerk/types': minor
---

[Experimental] Signals: Add support for calling `signIn.password()` without an identifier.
27 changes: 25 additions & 2 deletions packages/clerk-js/src/core/resources/SignIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -503,12 +503,18 @@ class SignInFuture implements SignInFutureResource {
submitPassword: this.submitResetPassword.bind(this),
};

fetchStatus: 'idle' | 'fetching' = 'idle';

Comment on lines +506 to +507

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

fetchStatus is never updated; wire it to network calls.

The property is added but never transitions to 'fetching'/'idle', limiting its usefulness as an observable.

As a pattern, set to 'fetching' before a request and back to 'idle' in finally blocks. See the password/finalize suggestions below for concrete diffs; apply similarly across other methods in SignInFuture.

🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 506-507, the
fetchStatus property is initialized but never updated; update SignInFuture
methods that perform network requests to set this.fetchStatus = 'fetching'
immediately before the async call and reset this.fetchStatus = 'idle' in a
finally block after the call (and also in error branches where applicable).
Apply this pattern to all SignInFuture methods that call the network (e.g.,
start, attempt, finalize, password/finalize variants), ensuring the status is
set to 'fetching' before awaiting the request and always cleared to 'idle' in
finally so observers reliably see transitions.

constructor(readonly resource: SignIn) {}

get status() {
return this.resource.status;
}

get availableStrategies() {
return this.resource.supportedFirstFactors ?? [];
}

async sendResetPasswordEmailCode(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
Expand DownExpand Up@@ -593,12 +599,13 @@ class SignInFuture implements SignInFutureResource {
}
}

async password({ identifier, password }: { identifier: string; password: string }): Promise<{ error: unknown }> {
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
const previousIdentifier = this.resource.identifier;
try {
await this.resource.__internal_basePost({
path: this.resource.pathRoot,
body: { identifier, password },
body: { identifier: identifier || previousIdentifier, password },
});
Comment on lines +602 to 609

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Bug: password() misses strategy and wrong endpoint; add identifier guard and use attempt_first_factor when applicable.

Posting to /client/sign_ins without action and without strategy: 'password' will likely be treated as a create-with-identifier request and ignore the password. Also, when neither an explicit nor prior identifier exists, fail fast with a clear error. Finally, use fetchStatus to reflect request lifecycle.

Apply this diff:

- async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {- eventBus.emit('resource:error', { resource: this.resource, error: null });- const previousIdentifier = this.resource.identifier;- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };- }+ async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {+ eventBus.emit('resource:error', { resource: this.resource, error: null });+ this.fetchStatus = 'fetching';+ const previousIdentifier = this.resource.identifier;+ const idToUse = identifier ?? previousIdentifier ?? undefined;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Additionally, consider a unit/integration test to cover:

  • password() with explicit identifier (no prior create)
  • password() without identifier after a prior create()
  • password() without identifier and without prior create() should return { error }

🏁 Script executed:

#!/bin/bashset -e
# Show the current password() implementation with surrounding context
rg -n 'async password' -C10 packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 952


🏁 Script executed:

#!/bin/bashset -e
# Search for other uses of `strategy` in SignIn.tsecho"=== strategy occurrences ==="
rg -n 'strategy' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for `action` parameter usages in SignIn.tsecho"=== action occurrences ==="
rg -n 'action:' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true

Length of output: 9787


🏁 Script executed:

#!/bin/bashset -e
# Find definition of __internal_basePost across the repo
rg -n 'function __internal_basePost' -C3
rg -n '__internal_basePost' -C3

Length of output: 51


🏁 Script executed:

#!/bin/bashset -e
# Search for fetchStatus in SignIn.ts
rg -n 'fetchStatus' -C3 packages/clerk-js/src/core/resources/SignIn.ts ||true# Search for fetchStatus in the entire repo
rg -n 'fetchStatus' -C3 .

Length of output: 23302


🏁 Script executed:

#!/bin/bashset -e
# Show first 50 lines of SignIn.ts to inspect class context
sed -n '1,50p' packages/clerk-js/src/core/resources/SignIn.ts

Length of output: 1430


Implement proper strategy, action, identifier guard, and fetchStatus in password()

  • Set fetchStatus to "fetching" at the start and reset to "idle" in a finally block.
  • Throw a clear error if neither an explicit identifier nor an existing session ID is available.
  • Include strategy: 'password' in the request body.
  • Use action: 'attempt_first_factor' when a session ID already exists.
  • (Optional) specify path: this.resource.pathRoot for the initial create.
  • Add tests for:
    • Explicit identifier without prior create()
    • Implicit identifier after a prior create()
    • Missing identifier before any create()

Diff:

--- a/packages/clerk-js/src/core/resources/SignIn.ts+++ b/packages/clerk-js/src/core/resources/SignIn.ts@@ -602,14 +602,38 @@
async password({ identifier, password }: { identifier?: string; password: string }): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
+ this.fetchStatus = 'fetching';
const previousIdentifier = this.resource.identifier;
- try {- await this.resource.__internal_basePost({- path: this.resource.pathRoot,- body: { identifier: identifier || previousIdentifier, password },- });- } catch (err: unknown) {- eventBus.emit('resource:error', { resource: this.resource, error: err });- return { error: err };- }-- return { error: null };+ const idToUse = identifier ?? previousIdentifier;+ try {+ if (!this.resource.id) {+ if (!idToUse) {+ throw new Error('Cannot attempt password without an identifier. Call create() first or provide one.');+ }+ // Create + password in one call+ await this.resource.__internal_basePost({+ path: this.resource.pathRoot,+ body: { strategy: 'password', identifier: idToUse, password },+ });+ } else {+ // Attempt first factor on existing sign-in+ await this.resource.__internal_basePost({+ body: { strategy: 'password', password },+ action: 'attempt_first_factor',+ });+ }+ } catch (err: unknown) {+ eventBus.emit('resource:error', { resource: this.resource, error: err });+ return { error: err };+ } finally {+ this.fetchStatus = 'idle';+ }++ return { error: null };+ }

Add corresponding unit/integration tests to cover all scenarios.

📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
constpreviousIdentifier=this.resource.identifier;
try{
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {identifier,password},
body: {identifier: identifier||previousIdentifier, password },
});
asyncpassword({ identifier, password }: {identifier?: string;password: string}): Promise<{error: unknown}>{
eventBus.emit('resource:error',{resource: this.resource,error: null});
this.fetchStatus='fetching';
constpreviousIdentifier=this.resource.identifier;
constidToUse=identifier??previousIdentifier;
try{
if(!this.resource.id){
if(!idToUse){
thrownewError(
'Cannot attempt password without an identifier. Call create() first or provide one.'
);
}
// Create + password in one call
awaitthis.resource.__internal_basePost({
path: this.resource.pathRoot,
body: {strategy: 'password',identifier: idToUse, password },
});
}else{
// Attempt first factor on existing sign-in
awaitthis.resource.__internal_basePost({
body: {strategy: 'password', password },
action: 'attempt_first_factor',
});
}
}catch(err: unknown){
eventBus.emit('resource:error',{resource: this.resource,error: err});
return{error: err};
}finally{
this.fetchStatus='idle';
}
return{error: null};
}
🤖 Prompt for AI Agents
In packages/clerk-js/src/core/resources/SignIn.ts around lines 602 to 609, set
this.resource.fetchStatus = "fetching" at the start of password() and ensure you
reset it to "idle" in a finally block; validate that either the explicit
identifier param or this.resource.identifier (session id/previous identifier)
exists and throw a clear error if neither is available; include strategy:
"password" in the request body and, when a previous identifier/session id
exists, add action: "attempt_first_factor" to the body (otherwise omit action
for initial create), and keep path: this.resource.pathRoot for the post; update
tests to cover explicit identifier without prior create(), implicit identifier
after prior create(), and missing identifier before any create().

} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
Expand DownExpand Up@@ -688,4 +695,20 @@ class SignInFuture implements SignInFutureResource {

return { error: null };
}

async finalize(): Promise<{ error: unknown }> {
eventBus.emit('resource:error', { resource: this.resource, error: null });
try {
if (!this.resource.createdSessionId) {
throw new Error('Cannot finalize sign-in without a created session.');
}

await SignIn.clerk.setActive({ session: this.resource.createdSessionId });
} catch (err: unknown) {
eventBus.emit('resource:error', { resource: this.resource, error: err });
return { error: err };
}

return { error: null };
}
}
2 changes: 1 addition & 1 deletion packages/shared/src/error.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -82,7 +82,7 @@ export function isKnownError(error: any): error is ClerkAPIResponseError | Clerk
* @internal
*/
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
return 'clerkError' in err;
return err && 'clerkError' in err;
}
Comment on lines 84 to 86

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Guard all type-narrowing helpers against falsy inputs to prevent runtime TypeErrors.

Good fix on isClerkAPIResponseError. However, isKnownError can still throw via isMetamaskError / isClerkRuntimeError when passed null/undefined, because the in operator on a non-object throws. Harden these as well.

Apply this diff:

 export function isKnownError(error: any): error is ClerkAPIResponseError | ClerkRuntimeError | MetamaskError {
- return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);+ // Guard against falsy input to avoid TypeError from `in` operator in downstream checks+ if (!error) {+ return false;+ }+ return isClerkAPIResponseError(error) || isMetamaskError(error) || isClerkRuntimeError(error);
}
export function isClerkAPIResponseError(err: any): err is ClerkAPIResponseError {
- return err && 'clerkError' in err;+ return !!err && typeof err === 'object' && 'clerkError' in err;
}
export function isClerkRuntimeError(err: any): err is ClerkRuntimeError {
- return 'clerkRuntimeError' in err;+ return !!err && typeof err === 'object' && 'clerkRuntimeError' in err;
}
export function isMetamaskError(err: any): err is MetamaskError {
- return 'code' in err && [4001, 32602, 32603].includes(err.code) && 'message' in err;+ return (+ !!err &&+ typeof err === 'object' &&+ 'code' in err &&+ [4001, 32602, 32603].includes((err as any).code) &&+ 'message' in err+ );
}

Also applies to: 105-106, 123-124, 75-77

🤖 Prompt for AI Agents
In packages/shared/src/error.ts around lines 75-77, 84-86, 105-106 and 123-124,
the type-narrowing helpers (isMetamaskError, isClerkAPIResponseError,
isClerkRuntimeError and isKnownError) use the `in` operator without first
checking for falsy/non-object inputs which can throw; update each helper to
first confirm the value is truthy and typeof value === 'object' (or use value !=
null) before using `in` or other object checks, and adjust isKnownError to call
the hardened helpers safely so it never calls `in` on null/undefined.


/**
Expand Down
12 changes: 10 additions & 2 deletions packages/types/src/signIn.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -126,9 +126,16 @@ export interface SignInResource extends ClerkResource {
}

export interface SignInFutureResource {
fetchStatus: 'idle' | 'fetching';
availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
Comment on lines +129 to 131

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add JSDoc for new public APIs (fetchStatus, availableStrategies, password identifier fallback, finalize).

Ensure discoverability and clarity for consumers.

Apply this diff to document the additions:

 export interface SignInFutureResource {
- fetchStatus: 'idle' | 'fetching';- availableStrategies: SignInFirstFactor[];+ /**+ * Reflects ongoing network activity for the future resource.+ * 'fetching' while an API call is in flight, otherwise 'idle'.+ * @experimental+ */+ fetchStatus: 'idle' | 'fetching';+ /**+ * Convenience mirror of SignIn.supportedFirstFactors. Always an array.+ * @experimental+ */+ availableStrategies: SignInFirstFactor[];
status: SignInStatus | null;
- create: (params: { identifier: string }) => Promise<{ error: unknown }>;- password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;+ /**+ * Attempts password auth. When identifier is omitted, falls back to the identifier+ * set during a previous create() call.+ * @experimental+ */+ password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
...
- finalize: () => Promise<{ error: unknown }>;+ /**+ * Activates the created session (SignIn.createdSessionId) via Clerk.setActive.+ * Returns `{ error: null }` on success or `{ error }` on failure.+ * @experimental+ */+ finalize: () => Promise<{ error: unknown }>;
}

Also applies to: 149-150, 133-134

🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 129-131 (also apply to lines
133-134 and 149-150), the new public API surface (fetchStatus,
availableStrategies, the password identifier fallback, and finalize) lacks JSDoc
comments; add clear JSDoc blocks above each exported property/function
describing its purpose, type/possible values (e.g., fetchStatus:
'idle'|'fetching'; availableStrategies: SignInFirstFactor[]), when it is
set/used, any fallback behavior for the password identifier, expected return
values/errors for finalize, and example usage/notes on stability so consumers
can discover and understand these members. Ensure each JSDoc includes a one-line
summary, @returns or @type where appropriate, and any relevant @default or
@remarks details.

create: (params: { identifier: string }) => Promise<{ error: unknown }>;
password: (params: { identifier: string; password: string }) => Promise<{ error: unknown }>;
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;
Comment on lines +132 to +137

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Document the expanded create() params and verify redirect param naming consistency.

The widened signature looks correct; please add JSDoc describing each param and its behavior. Additionally, the naming differs from sso() which uses redirectUrlComplete while create() uses actionCompleteRedirectUrl. If this is intentional, document the distinction; otherwise consider aligning or supporting both (runtime-dependent).

Apply this diff to document the params:

 status: SignInStatus | null;
- create: (params: {+ /**+ * Starts a sign-in with optional identifier and/or federated SSO strategy.+ * When `strategy` is provided (OAuth/SAML/enterprise SSO), `redirectUrl` and+ * optionally `actionCompleteRedirectUrl` control the return locations.+ * `identifier` is optional here to support flows that defer it to a later step.+ * @experimental+ */+ create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
}) => Promise<{ error: unknown }>;

Run this to check for naming consistency across the repo:


🏁 Script executed:

#!/bin/bash# Find occurrences of these redirect params to confirm intent and consistency.
rg -n -S 'redirectUrlComplete|actionCompleteRedirectUrl' -A2 -B2

Length of output: 50926


Document create() params and unify redirect param naming

Please add JSDoc to the new create() signature in packages/types/src/signIn.ts and resolve the discrepancy between its actionCompleteRedirectUrl and the rest of the APIs’ redirectUrlComplete. Either rename actionCompleteRedirectUrl to redirectUrlComplete (or alias it), or clearly document why this method diverges.

• File: packages/types/src/signIn.ts (lines 132–137)
• Compare with sso() at lines 150–152 (uses redirectUrlComplete)

Proposed diff for JSDoc and aliasing:

 status: SignInStatus | null;
+ /**+ * Starts a sign-in flow, optionally with an identifier or federated SSO strategy.+ * @param params.identifier Optional user identifier for credentialed sign-in.+ * @param params.strategy OAuth/SAML/enterprise SSO strategy.+ * @param params.redirectUrl URL to return to after initiating the flow.+ * @param params.redirectUrlComplete+ * Full URL or path to navigate to once sign-in completes. Alias for+ * `actionCompleteRedirectUrl` to align with other APIs.+ * @experimental+ */
create: (params: {
identifier?: string;
strategy?: OAuthStrategy | 'saml' | 'enterprise_sso';
redirectUrl?: string;
- actionCompleteRedirectUrl?: string;+ actionCompleteRedirectUrl?: string;+ redirectUrlComplete?: string;
}) => Promise<{ error: unknown }>;

Additionally, review and update the same pattern in:

  • packages/types/src/signUp.ts
  • packages/types/src/factors.ts
  • Any other user-facing methods that accept completion URLs
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
})=>Promise<{error: unknown}>;
status: SignInStatus|null;
/**
*Startsasign-inflow,optionallywithanidentifierorfederatedSSOstrategy.
* @paramparams.identifierOptionaluseridentifierforcredentialedsign-in.
* @paramparams.strategyOAuth/SAML/enterpriseSSOstrategy.
* @paramparams.redirectUrlURLtoreturntoafterinitiatingtheflow.
* @paramparams.redirectUrlCompleteFullURLorpathtonavigatetooncesign-incompletes.
*Aliasfor`actionCompleteRedirectUrl`toalignwithotherAPIs.
* @experimental
*/
create: (params: {
identifier?: string;
strategy?: OAuthStrategy|'saml'|'enterprise_sso';
redirectUrl?: string;
actionCompleteRedirectUrl?: string;
redirectUrlComplete?: string;
})=>Promise<{error: unknown}>;
🤖 Prompt for AI Agents
In packages/types/src/signIn.ts around lines 132 to 137, the create() signature
lacks JSDoc and uses actionCompleteRedirectUrl which is inconsistent with other
APIs that use redirectUrlComplete (see sso() at lines 150–152); add a concise
JSDoc block describing each param and the returned Promise, and rename the param
to redirectUrlComplete (or add redirectUrlComplete as an alias that maps to the
existing actionCompleteRedirectUrl) so naming is consistent; apply the same
JSDoc + rename/alias pattern to packages/types/src/signUp.ts,
packages/types/src/factors.ts, and any other user-facing methods that accept
completion/redirect URLs to keep the public API consistent.

password: (params: { identifier?: string; password: string }) => Promise<{ error: unknown }>;
emailCode: {
sendCode: (params: { email: string }) => Promise<{ error: unknown }>;
verifyCode: (params: { code: string }) => Promise<{ error: unknown }>;
Expand All@@ -144,6 +151,7 @@ export interface SignInFutureResource {
redirectUrl: string;
redirectUrlComplete: string;
}) => Promise<{ error: unknown }>;
finalize: () => Promise<{ error: unknown }>;
}

export type SignInStatus =
Expand Down
Loading