Skip to content

Commit c1752c1

Browse files
zourzouvillysclaude
andcommitted
feat(js,shared,ui): Add protect check as missing requirement with prepare/attempt pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 37a4cbb commit c1752c1

17 files changed

Lines changed: 779 additions & 3 deletions

File tree

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

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { inBrowser } from '@clerk/shared/browser';
22
import { type ClerkError, ClerkRuntimeError, isCaptchaError, isClerkAPIResponseError } from '@clerk/shared/error';
3+
import { PROTECT_CHECK_CONTAINER_ID } from '@clerk/shared/internal/clerk-js/constants';
34
import { createValidatePassword } from '@clerk/shared/internal/clerk-js/passwords/password';
45
import { windowNavigate } from '@clerk/shared/internal/clerk-js/windowNavigate';
56
import { Poller } from '@clerk/shared/poller';
@@ -56,6 +57,7 @@ import {
5657
} from '../../utils/authenticateWithPopup';
5758
import { CaptchaChallenge } from '../../utils/captcha/CaptchaChallenge';
5859
import { normalizeUnsafeMetadata } from '../../utils/resourceParams';
60+
import { executeProtectCheck } from '../../utils/protectCheck';
5961
import { runAsyncResourceTask } from '../../utils/runAsyncResourceTask';
6062
import { loadZxcvbn } from '../../utils/zxcvbn';
6163
import {
@@ -480,6 +482,36 @@ export class SignUp extends BaseResource implements SignUpResource {
480482
});
481483
};
482484

485+
runProtectCheck = async (): Promise<SignUpResource> => {
486+
// 1. Prepare — backend returns script URL in verifications.protect_check
487+
await this._basePost({ action: 'prepare_protect_check' });
488+
489+
const scriptUrl = this.verifications.protectCheck?.url;
490+
if (!scriptUrl) {
491+
throw new ClerkRuntimeError('No protect check script URL returned', {
492+
code: 'protect_check_missing_url',
493+
});
494+
}
495+
496+
// 2. Get or create container
497+
const container = this.getOrCreateProtectCheckContainer();
498+
499+
try {
500+
// 3. Load and execute script
501+
const result = await executeProtectCheck(scriptUrl, this, container);
502+
503+
// 4. Attempt with result
504+
await this._basePost({
505+
body: result,
506+
action: 'attempt_protect_check',
507+
});
508+
} finally {
509+
this.cleanupProtectCheckContainer(container);
510+
}
511+
512+
return this;
513+
};
514+
483515
upsert = (params: SignUpCreateParams | SignUpUpdateParams): Promise<SignUpResource> => {
484516
return this.id ? this.update(params) : this.create(params);
485517
};
@@ -583,6 +615,25 @@ export class SignUp extends BaseResource implements SignUpResource {
583615
return false;
584616
}
585617

618+
private getOrCreateProtectCheckContainer(): HTMLDivElement {
619+
let el = document.getElementById(PROTECT_CHECK_CONTAINER_ID) as HTMLDivElement | null;
620+
if (!el) {
621+
el = document.createElement('div');
622+
el.id = PROTECT_CHECK_CONTAINER_ID;
623+
document.body.appendChild(el);
624+
}
625+
return el;
626+
}
627+
628+
private cleanupProtectCheckContainer(el: HTMLDivElement) {
629+
// Only remove from DOM if we created it (i.e., the UI didn't provide it)
630+
if (el.parentNode && !document.getElementById(PROTECT_CHECK_CONTAINER_ID)) {
631+
el.remove();
632+
}
633+
// Always clear inner content
634+
el.innerHTML = '';
635+
}
636+
586637
__experimental_getEnterpriseConnections = (): Promise<SignUpEnterpriseConnectionResource[]> => {
587638
return BaseResource._fetch({
588639
path: `/client/sign_ups/${this.id}/enterprise_connections`,
@@ -603,6 +654,7 @@ type SignUpFutureVerificationsMethods = Pick<
603654
| 'waitForEmailLinkVerification'
604655
| 'sendPhoneCode'
605656
| 'verifyPhoneCode'
657+
| 'runProtectCheck'
606658
>;
607659

608660
class SignUpFutureVerifications implements SignUpFutureVerificationsType {
@@ -614,6 +666,7 @@ class SignUpFutureVerifications implements SignUpFutureVerificationsType {
614666
waitForEmailLinkVerification: SignUpFutureVerificationsType['waitForEmailLinkVerification'];
615667
sendPhoneCode: SignUpFutureVerificationsType['sendPhoneCode'];
616668
verifyPhoneCode: SignUpFutureVerificationsType['verifyPhoneCode'];
669+
runProtectCheck: SignUpFutureVerificationsType['runProtectCheck'];
617670

618671
constructor(resource: SignUp, methods: SignUpFutureVerificationsMethods) {
619672
this.#resource = resource;
@@ -623,6 +676,7 @@ class SignUpFutureVerifications implements SignUpFutureVerificationsType {
623676
this.waitForEmailLinkVerification = methods.waitForEmailLinkVerification;
624677
this.sendPhoneCode = methods.sendPhoneCode;
625678
this.verifyPhoneCode = methods.verifyPhoneCode;
679+
this.runProtectCheck = methods.runProtectCheck;
626680
}
627681

628682
get emailAddress() {
@@ -641,6 +695,10 @@ class SignUpFutureVerifications implements SignUpFutureVerificationsType {
641695
return this.#resource.verifications.externalAccount;
642696
}
643697

698+
get protectCheck() {
699+
return this.#resource.verifications.protectCheck;
700+
}
701+
644702
get emailLinkVerification() {
645703
if (!inBrowser()) {
646704
return null;
@@ -681,6 +739,7 @@ class SignUpFuture implements SignUpFutureResource {
681739
waitForEmailLinkVerification: this.waitForEmailLinkVerification.bind(this),
682740
sendPhoneCode: this.sendPhoneCode.bind(this),
683741
verifyPhoneCode: this.verifyPhoneCode.bind(this),
742+
runProtectCheck: this._runProtectCheck.bind(this),
684743
});
685744
}
686745

@@ -832,6 +891,46 @@ class SignUpFuture implements SignUpFutureResource {
832891
return { captchaToken, captchaWidgetType, captchaError };
833892
}
834893

894+
private async _runProtectCheck(): Promise<{ error: ClerkError | null }> {
895+
return runAsyncResourceTask(this.#resource, async () => {
896+
// 1. Prepare
897+
await this.#resource.__internal_basePost({ action: 'prepare_protect_check' });
898+
899+
const scriptUrl = this.#resource.verifications.protectCheck?.url;
900+
if (!scriptUrl) {
901+
throw new ClerkRuntimeError('No protect check script URL returned', {
902+
code: 'protect_check_missing_url',
903+
});
904+
}
905+
906+
// 2. Container
907+
let container = document.getElementById(PROTECT_CHECK_CONTAINER_ID) as HTMLDivElement | null;
908+
const createdContainer = !container;
909+
if (!container) {
910+
container = document.createElement('div');
911+
container.id = PROTECT_CHECK_CONTAINER_ID;
912+
document.body.appendChild(container);
913+
}
914+
915+
try {
916+
// 3. Execute script
917+
const result = await executeProtectCheck(scriptUrl, this.#resource, container);
918+
919+
// 4. Attempt
920+
await this.#resource.__internal_basePost({
921+
body: result,
922+
action: 'attempt_protect_check',
923+
});
924+
} finally {
925+
if (createdContainer) {
926+
container.remove();
927+
} else {
928+
container.innerHTML = '';
929+
}
930+
}
931+
});
932+
}
933+
835934
private async _create(params: SignUpFutureCreateParams): Promise<void> {
836935
const { captchaToken, captchaWidgetType, captchaError } = await this.getCaptchaToken(params);
837936

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,18 +107,21 @@ export class SignUpVerifications implements SignUpVerificationsResource {
107107
phoneNumber: SignUpVerificationResource;
108108
web3Wallet: SignUpVerificationResource;
109109
externalAccount: VerificationResource;
110+
protectCheck: { url: string } | null;
110111

111112
constructor(data: SignUpVerificationsJSON | SignUpVerificationsJSONSnapshot | null) {
112113
if (data) {
113114
this.emailAddress = new SignUpVerification(data.email_address);
114115
this.phoneNumber = new SignUpVerification(data.phone_number);
115116
this.web3Wallet = new SignUpVerification(data.web3_wallet);
116117
this.externalAccount = new Verification(data.external_account);
118+
this.protectCheck = data.protect_check ?? null;
117119
} else {
118120
this.emailAddress = new SignUpVerification(null);
119121
this.phoneNumber = new SignUpVerification(null);
120122
this.web3Wallet = new SignUpVerification(null);
121123
this.externalAccount = new Verification(null);
124+
this.protectCheck = null;
122125
}
123126
}
124127

@@ -128,6 +131,7 @@ export class SignUpVerifications implements SignUpVerificationsResource {
128131
phone_number: this.phoneNumber.__internal_toSnapshot(),
129132
web3_wallet: this.web3Wallet.__internal_toSnapshot(),
130133
external_account: this.externalAccount.__internal_toSnapshot(),
134+
protect_check: this.protectCheck,
131135
};
132136
}
133137
}

0 commit comments

Comments
 (0)