@volter/twin-clerk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +102 -0
- package/client/clerk-js-dist/167_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/2172_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/2172_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/368_clerk.headless_b86dff_5.127.2.js +3 -0
- package/client/clerk-js-dist/377_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/4170_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/4170_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/437_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/5192_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/5192_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/553_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/5645_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/5645_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/623_clerk.headless_b86dff_5.127.2.js +42 -0
- package/client/clerk-js-dist/7067_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/7067_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/7502_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/7502_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/785_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/785_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/8043_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/825_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/825_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/890_clerk.headless_b86dff_5.127.2.js +2 -0
- package/client/clerk-js-dist/901_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/apiKeys_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/apiKeys_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/base-account-sdk_clerk.browser_0cc2cc_5.127.2.js +42 -0
- package/client/clerk-js-dist/base-account-sdk_clerk.headless.browser_540199_5.127.2.js +42 -0
- package/client/clerk-js-dist/base-account-sdk_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/blankcaptcha_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/blankcaptcha_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/checkout_clerk.browser_0cc2cc_5.127.2.js +9 -0
- package/client/clerk-js-dist/checkout_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/clerk.browser.js +19 -0
- package/client/clerk-js-dist/clerk.headless.browser.js +19 -0
- package/client/clerk-js-dist/clerk.headless.js +19 -0
- package/client/clerk-js-dist/clerk.js +809 -0
- package/client/clerk-js-dist/clerk.legacy.browser.js +1 -0
- package/client/clerk-js-dist/clerk.no-rhc.js +766 -0
- package/client/clerk-js-dist/coinbase-wallet-sdk_clerk.browser_0cc2cc_5.127.2.js +3 -0
- package/client/clerk-js-dist/coinbase-wallet-sdk_clerk.headless.browser_540199_5.127.2.js +3 -0
- package/client/clerk-js-dist/coinbase-wallet-sdk_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/cookieSuffix_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/cookieSuffix_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/cookieSuffix_clerk.headless_b86dff_5.127.2.js +1 -0
- package/client/clerk-js-dist/cookieSuffix_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/copy-api-key-modal_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/copy-api-key-modal_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/createorganization_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/createorganization_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/enableOrganizationsPrompt_clerk.browser_0cc2cc_5.127.2.js +247 -0
- package/client/clerk-js-dist/enableOrganizationsPrompt_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/framework_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/framework_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/impersonationfab_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/impersonationfab_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/keylessPrompt_clerk.browser_0cc2cc_5.127.2.js +228 -0
- package/client/clerk-js-dist/keylessPrompt_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/oauthConsent_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/oauthConsent_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/onetap_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/onetap_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/op-api-keys-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/op-api-keys-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/op-billing-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/op-billing-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/op-plans-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/op-plans-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/organizationlist_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/organizationlist_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/organizationprofile_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/organizationprofile_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/organizationswitcher_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/organizationswitcher_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/payment-attempt-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/payment-attempt-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/planDetails_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/planDetails_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/prefetchorganizationlist_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/prefetchorganizationlist_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/pricingTable_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/pricingTable_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/query-core-vendors_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/query-core-vendors_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/query-core-vendors_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/revoke-api-key-modal_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/revoke-api-key-modal_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/sessionTasks_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/sessionTasks_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/signin_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/signin_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/signup_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/signup_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/statement-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/statement-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/stripe-vendors_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/stripe-vendors_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/stripe-vendors_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/subscriptionDetails_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/subscriptionDetails_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/taskChooseOrganization_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/taskChooseOrganization_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/taskResetPassword_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/taskResetPassword_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/taskSetupMFA_clerk.browser_0cc2cc_5.127.2.js +4 -0
- package/client/clerk-js-dist/taskSetupMFA_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/ui-common_clerk.browser_0cc2cc_5.127.2.js +124 -0
- package/client/clerk-js-dist/ui-common_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/up-api-keys-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/up-api-keys-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/up-billing-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/up-billing-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/up-plans-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/up-plans-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/useravatar_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/useravatar_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/userbutton_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/userbutton_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/userprofile_clerk.browser_0cc2cc_5.127.2.js +4 -0
- package/client/clerk-js-dist/userprofile_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/userverification_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/userverification_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/vendors_clerk.browser_0cc2cc_5.127.2.js +20 -0
- package/client/clerk-js-dist/vendors_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/vendors_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/waitlist_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/waitlist_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/web3-solana-wallet-buttons_clerk.browser_0cc2cc_5.127.2.js +143 -0
- package/client/clerk-js-dist/web3-solana-wallet-buttons_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/zxcvbn-common_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/client/clerk-js-dist/zxcvbn-common_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/client/clerk-js-dist/zxcvbn-common_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-js-dist/zxcvbn-ts-core_clerk.browser_0cc2cc_5.127.2.js +2 -0
- package/client/clerk-js-dist/zxcvbn-ts-core_clerk.headless.browser_540199_5.127.2.js +2 -0
- package/client/clerk-js-dist/zxcvbn-ts-core_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/client/clerk-mirror.css +72 -0
- package/client/clerk-mirror.tsx +204 -0
- package/client/clerk.browser.js +19 -0
- package/dist/client/clerk-js-dist/167_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/2172_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/2172_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/368_clerk.headless_b86dff_5.127.2.js +3 -0
- package/dist/client/clerk-js-dist/377_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/4170_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/4170_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/437_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/5192_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/5192_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/553_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/5645_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/5645_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/623_clerk.headless_b86dff_5.127.2.js +42 -0
- package/dist/client/clerk-js-dist/7067_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/7067_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/7502_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/7502_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/785_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/785_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/8043_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/825_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/825_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/890_clerk.headless_b86dff_5.127.2.js +2 -0
- package/dist/client/clerk-js-dist/901_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/apiKeys_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/apiKeys_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/base-account-sdk_clerk.browser_0cc2cc_5.127.2.js +42 -0
- package/dist/client/clerk-js-dist/base-account-sdk_clerk.headless.browser_540199_5.127.2.js +42 -0
- package/dist/client/clerk-js-dist/base-account-sdk_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/blankcaptcha_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/blankcaptcha_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/checkout_clerk.browser_0cc2cc_5.127.2.js +9 -0
- package/dist/client/clerk-js-dist/checkout_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/clerk.browser.js +19 -0
- package/dist/client/clerk-js-dist/clerk.headless.browser.js +19 -0
- package/dist/client/clerk-js-dist/clerk.headless.js +19 -0
- package/dist/client/clerk-js-dist/clerk.js +809 -0
- package/dist/client/clerk-js-dist/clerk.legacy.browser.js +1 -0
- package/dist/client/clerk-js-dist/clerk.no-rhc.js +766 -0
- package/dist/client/clerk-js-dist/coinbase-wallet-sdk_clerk.browser_0cc2cc_5.127.2.js +3 -0
- package/dist/client/clerk-js-dist/coinbase-wallet-sdk_clerk.headless.browser_540199_5.127.2.js +3 -0
- package/dist/client/clerk-js-dist/coinbase-wallet-sdk_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/cookieSuffix_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/cookieSuffix_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/cookieSuffix_clerk.headless_b86dff_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/cookieSuffix_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/copy-api-key-modal_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/copy-api-key-modal_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/createorganization_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/createorganization_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/enableOrganizationsPrompt_clerk.browser_0cc2cc_5.127.2.js +247 -0
- package/dist/client/clerk-js-dist/enableOrganizationsPrompt_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/framework_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/framework_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/impersonationfab_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/impersonationfab_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/keylessPrompt_clerk.browser_0cc2cc_5.127.2.js +228 -0
- package/dist/client/clerk-js-dist/keylessPrompt_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/oauthConsent_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/oauthConsent_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/onetap_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/onetap_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/op-api-keys-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/op-api-keys-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/op-billing-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/op-billing-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/op-plans-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/op-plans-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/organizationlist_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/organizationlist_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/organizationprofile_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/organizationprofile_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/organizationswitcher_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/organizationswitcher_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/payment-attempt-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/payment-attempt-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/planDetails_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/planDetails_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/prefetchorganizationlist_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/prefetchorganizationlist_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/pricingTable_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/pricingTable_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/query-core-vendors_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/query-core-vendors_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/query-core-vendors_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/revoke-api-key-modal_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/revoke-api-key-modal_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/sessionTasks_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/sessionTasks_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/signin_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/signin_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/signup_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/signup_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/statement-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/statement-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/stripe-vendors_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/stripe-vendors_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/stripe-vendors_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/subscriptionDetails_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/subscriptionDetails_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/taskChooseOrganization_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/taskChooseOrganization_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/taskResetPassword_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/taskResetPassword_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/taskSetupMFA_clerk.browser_0cc2cc_5.127.2.js +4 -0
- package/dist/client/clerk-js-dist/taskSetupMFA_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/ui-common_clerk.browser_0cc2cc_5.127.2.js +124 -0
- package/dist/client/clerk-js-dist/ui-common_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/up-api-keys-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/up-api-keys-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/up-billing-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/up-billing-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/up-plans-page_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/up-plans-page_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/useravatar_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/useravatar_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/userbutton_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/userbutton_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/userprofile_clerk.browser_0cc2cc_5.127.2.js +4 -0
- package/dist/client/clerk-js-dist/userprofile_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/userverification_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/userverification_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/vendors_clerk.browser_0cc2cc_5.127.2.js +20 -0
- package/dist/client/clerk-js-dist/vendors_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/vendors_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/waitlist_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/waitlist_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/web3-solana-wallet-buttons_clerk.browser_0cc2cc_5.127.2.js +143 -0
- package/dist/client/clerk-js-dist/web3-solana-wallet-buttons_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/zxcvbn-common_clerk.browser_0cc2cc_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/zxcvbn-common_clerk.headless.browser_540199_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/zxcvbn-common_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-js-dist/zxcvbn-ts-core_clerk.browser_0cc2cc_5.127.2.js +2 -0
- package/dist/client/clerk-js-dist/zxcvbn-ts-core_clerk.headless.browser_540199_5.127.2.js +2 -0
- package/dist/client/clerk-js-dist/zxcvbn-ts-core_clerk.legacy.browser_6070dd_5.127.2.js +1 -0
- package/dist/client/clerk-mirror.bundle.js +321 -0
- package/dist/client/clerk-mirror.css +72 -0
- package/dist/client/clerk-mirror.d.ts +33 -0
- package/dist/client/clerk-mirror.js +143 -0
- package/dist/client/clerk-mirror.tsx +204 -0
- package/dist/client/clerk.browser.js +19 -0
- package/dist/src/clerk-capabilities.d.ts +4 -0
- package/dist/src/clerk-capabilities.js +1132 -0
- package/dist/src/clerk-conformance.d.ts +32 -0
- package/dist/src/clerk-conformance.js +158 -0
- package/dist/src/clerk-connector.d.ts +170 -0
- package/dist/src/clerk-connector.js +223 -0
- package/dist/src/clerk-events.d.ts +55 -0
- package/dist/src/clerk-events.js +146 -0
- package/dist/src/clerk-fapi.d.ts +19 -0
- package/dist/src/clerk-fapi.js +665 -0
- package/dist/src/clerk-jwt.d.ts +109 -0
- package/dist/src/clerk-jwt.js +230 -0
- package/dist/src/clerk-mirror-ui.d.ts +34 -0
- package/dist/src/clerk-mirror-ui.js +160 -0
- package/dist/src/clerk-server.d.ts +35 -0
- package/dist/src/clerk-server.js +172 -0
- package/dist/src/clerk-twin.d.ts +28 -0
- package/dist/src/clerk-twin.js +1658 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +35 -0
- package/dist/src/index.d.ts +12 -0
- package/dist/src/index.js +56 -0
- package/dist/test-fixtures/clerk-openapi-operations.SOURCE.md +16 -0
- package/dist/test-fixtures/clerk-openapi-operations.json +1989 -0
- package/package.json +76 -0
- package/src/clerk-capabilities.ts +1246 -0
- package/src/clerk-conformance.ts +172 -0
- package/src/clerk-connector.ts +268 -0
- package/src/clerk-events.ts +166 -0
- package/src/clerk-fapi.ts +673 -0
- package/src/clerk-jwt.ts +256 -0
- package/src/clerk-mirror-ui.ts +165 -0
- package/src/clerk-server.ts +175 -0
- package/src/clerk-twin.ts +1580 -0
- package/src/cli.ts +33 -0
- package/src/index.ts +94 -0
- package/test-fixtures/clerk-openapi-operations.SOURCE.md +16 -0
- package/test-fixtures/clerk-openapi-operations.json +1989 -0
package/src/clerk-jwt.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// Clerk JWT / JWKS — the highest-value capability of the Clerk twin.
|
|
2
|
+
//
|
|
3
|
+
// Real Clerk issues RS256-signed JWTs for sessions and JWT templates, and serves a
|
|
4
|
+
// JWKS document (/.well-known/jwks.json) an app verifies those tokens against. A
|
|
5
|
+
// faithful twin must do the SAME with REAL crypto: generate an RSA keypair on first
|
|
6
|
+
// use, persist it (via the kernel state, so it survives a process restart and is the
|
|
7
|
+
// SAME key for both signing and the served JWKS), sign tokens as genuine JWTs, and
|
|
8
|
+
// serve a genuine JWKS — so an app verifying a twin-issued token against the twin's
|
|
9
|
+
// JWKS succeeds UNMODIFIED (`jwt.verify(token, jwks)` works, claims round-trip).
|
|
10
|
+
//
|
|
11
|
+
// `node:crypto` is required LAZILY (inside the functions that use it), NOT as a
|
|
12
|
+
// top-level static import, so this module can be pulled into the browser bundle of
|
|
13
|
+
// the UI mirror without Bun's browser target choking on `node:crypto` — the signing
|
|
14
|
+
// helpers are server-only and never reached in the browser path.
|
|
15
|
+
import { applyTwinWrite, projectResources, nodeBuiltin } from '@volter/world-core';
|
|
16
|
+
|
|
17
|
+
type NodeCrypto = typeof import('node:crypto');
|
|
18
|
+
function nodeCrypto(): NodeCrypto {
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
|
+
return nodeBuiltin('node:crypto') as NodeCrypto;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const SERVICE = 'clerk';
|
|
24
|
+
// The keypair is stored as a single twin-internal resource so it persists in kernel
|
|
25
|
+
// state (the SAME substrate the rest of the twin uses — no parallel store). Its id is
|
|
26
|
+
// fixed; the `_`-prefixed fields are twin-internal (the conformance gate exempts them).
|
|
27
|
+
const KEYPAIR_TYPE = '_keypair';
|
|
28
|
+
const KEYPAIR_ID = 'clerk_signing_key';
|
|
29
|
+
|
|
30
|
+
export type ClerkKeypair = { kid: string; publicKeyPem: string; privateKeyPem: string };
|
|
31
|
+
|
|
32
|
+
/** The PEM material of an RSA signing keypair (no kid — the kid is derived from it). */
|
|
33
|
+
export type KeypairMaterial = { publicKeyPem: string; privateKeyPem: string };
|
|
34
|
+
/** How `ensureKeypair` mints a NEW keypair on first use. */
|
|
35
|
+
export type KeypairGenerator = () => KeypairMaterial;
|
|
36
|
+
|
|
37
|
+
/** DEFAULT keypair source: a fresh RSA-2048 keypair from node:crypto. This is what every
|
|
38
|
+
* Node/Bun twin uses — generate-on-first-use, unchanged. */
|
|
39
|
+
function nodeGenerateKeypair(): KeypairMaterial {
|
|
40
|
+
const { generateKeyPairSync } = nodeCrypto();
|
|
41
|
+
const { publicKey, privateKey } = generateKeyPairSync('rsa', {
|
|
42
|
+
modulusLength: 2048,
|
|
43
|
+
publicKeyEncoding: { type: 'spki', format: 'pem' },
|
|
44
|
+
privateKeyEncoding: { type: 'pkcs8', format: 'pem' },
|
|
45
|
+
});
|
|
46
|
+
return { publicKeyPem: publicKey as string, privateKeyPem: privateKey as string };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Injectable keypair seam (mirrors the WorldStore seam). The DEFAULT is node:crypto keygen,
|
|
50
|
+
// so the Node/Bun path and the conformance suite are byte-identical. A serverless host where
|
|
51
|
+
// RUNTIME RSA keygen is not viable installs a provider returning a PRE-GENERATED keypair:
|
|
52
|
+
// measured on the real Cloudflare edge, a single RSA-2048 `generateKeyPairSync` trips the
|
|
53
|
+
// free-plan 10ms CPU limit (HTTP 503, error 1102) ~28% of the time, whereas SIGNING once,
|
|
54
|
+
// `createPublicKey`/`createHash`, and the JWK export are all cheap and reliable. Baking the
|
|
55
|
+
// keypair removes the only heavyweight op from the edge; the kid is still derived from the
|
|
56
|
+
// public key by `kidFor`, so a stable baked key yields a STABLE kid (and it is persisted in
|
|
57
|
+
// kernel state on first use exactly as a generated one would be).
|
|
58
|
+
const keypairGenerator = { current: nodeGenerateKeypair as KeypairGenerator }; // a slot, not module truth (protocol 2)
|
|
59
|
+
|
|
60
|
+
/** Override how `ensureKeypair` mints a new keypair; pass `null` to restore the node:crypto
|
|
61
|
+
* default. Workers-only wiring — the default keeps generate-on-first-use behavior. */
|
|
62
|
+
export function setKeypairGenerator(generator: KeypairGenerator | null): void {
|
|
63
|
+
keypairGenerator.current = generator ?? nodeGenerateKeypair;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The twin's RSA signing keypair for `root`, generated + persisted on first use and
|
|
68
|
+
* stable thereafter (persisted in kernel state, so signing and the served JWKS share
|
|
69
|
+
* ONE key). Deterministic kid derived from the public key's modulus, so the JWKS `kid`
|
|
70
|
+
* matches the token header `kid` — what a verifier uses to pick the key.
|
|
71
|
+
*/
|
|
72
|
+
export async function ensureKeypair(root?: string): Promise<ClerkKeypair> {
|
|
73
|
+
const existing = projectResources(SERVICE, root).find((r) => r.type === KEYPAIR_TYPE && r.id === KEYPAIR_ID);
|
|
74
|
+
if (existing && typeof existing._privateKeyPem === 'string' && typeof existing._publicKeyPem === 'string') {
|
|
75
|
+
return { kid: String(existing._kid), publicKeyPem: existing._publicKeyPem, privateKeyPem: existing._privateKeyPem };
|
|
76
|
+
}
|
|
77
|
+
const { publicKeyPem, privateKeyPem } = keypairGenerator.current();
|
|
78
|
+
const kid = kidFor(publicKeyPem);
|
|
79
|
+
await applyTwinWrite(
|
|
80
|
+
SERVICE,
|
|
81
|
+
{
|
|
82
|
+
operation: '_keypair.create',
|
|
83
|
+
subjectType: KEYPAIR_TYPE,
|
|
84
|
+
subjectId: KEYPAIR_ID,
|
|
85
|
+
fields: { _kid: kid, _publicKeyPem: publicKeyPem, _privateKeyPem: privateKeyPem },
|
|
86
|
+
actor: { kind: 'system' },
|
|
87
|
+
},
|
|
88
|
+
root,
|
|
89
|
+
);
|
|
90
|
+
return { kid, publicKeyPem, privateKeyPem };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Stable key id = first 16 hex of SHA-256 over the public key DER (deterministic per key). */
|
|
94
|
+
function kidFor(publicKeyPem: string): string {
|
|
95
|
+
const { createHash, createPublicKey } = nodeCrypto();
|
|
96
|
+
const der = createPublicKey(publicKeyPem).export({ type: 'spki', format: 'der' });
|
|
97
|
+
return `ins_${createHash('sha256').update(der).digest('hex').slice(0, 16)}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function base64url(input: Buffer | string): string {
|
|
101
|
+
return Buffer.from(input).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
102
|
+
}
|
|
103
|
+
function base64urlJson(obj: unknown): string {
|
|
104
|
+
return base64url(JSON.stringify(obj));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Sign a JWT (RS256) with the twin's persisted private key. The header carries the
|
|
109
|
+
* `kid` of the served JWKS so a verifier picks the right key. Returns the compact
|
|
110
|
+
* `header.payload.signature` token. Pure local crypto — no network.
|
|
111
|
+
*/
|
|
112
|
+
export async function signJwt(claims: Record<string, unknown>, opts: { root?: string; expiresInSeconds?: number; now?: number } = {}): Promise<string> {
|
|
113
|
+
const { createSign } = nodeCrypto();
|
|
114
|
+
const { kid, privateKeyPem } = await ensureKeypair(opts.root);
|
|
115
|
+
const iat = opts.now ?? Math.floor(Date.now() / 1000);
|
|
116
|
+
const exp = iat + (opts.expiresInSeconds ?? 60);
|
|
117
|
+
const header = { alg: 'RS256', typ: 'JWT', kid };
|
|
118
|
+
const payload = { iat, exp, nbf: iat, ...claims };
|
|
119
|
+
const signingInput = `${base64urlJson(header)}.${base64urlJson(payload)}`;
|
|
120
|
+
const signer = createSign('RSA-SHA256');
|
|
121
|
+
signer.update(signingInput);
|
|
122
|
+
signer.end();
|
|
123
|
+
const signature = base64url(signer.sign(privateKeyPem));
|
|
124
|
+
return `${signingInput}.${signature}`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Sign a JWT with a bring-your-own HS256 secret (a JWT template's `custom_signing_key`).
|
|
129
|
+
* Real Clerk lets a template carry its own signing algorithm + secret (e.g. Supabase's
|
|
130
|
+
* shared HS256 secret); the twin signs with genuine HMAC-SHA256 so the downstream service
|
|
131
|
+
* verifies it with the SAME secret unmodified. No persisted keypair, no JWKS (HS256 is
|
|
132
|
+
* symmetric). Pure local crypto — no network.
|
|
133
|
+
*/
|
|
134
|
+
export function signJwtHs256(claims: Record<string, unknown>, secret: string, opts: { expiresInSeconds?: number; now?: number } = {}): string {
|
|
135
|
+
const { createHmac } = nodeCrypto();
|
|
136
|
+
const iat = opts.now ?? Math.floor(Date.now() / 1000);
|
|
137
|
+
const exp = iat + (opts.expiresInSeconds ?? 60);
|
|
138
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
139
|
+
const payload = { iat, exp, nbf: iat, ...claims };
|
|
140
|
+
const signingInput = `${base64urlJson(header)}.${base64urlJson(payload)}`;
|
|
141
|
+
const signature = base64url(createHmac('sha256', secret).update(signingInput).digest());
|
|
142
|
+
return `${signingInput}.${signature}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Verify an HS256 JWT against the shared secret (constant-time) and check exp/nbf —
|
|
147
|
+
* what a downstream service does with a `custom_signing_key` template token. Pure local
|
|
148
|
+
* crypto — no network.
|
|
149
|
+
*/
|
|
150
|
+
export function verifyJwtHs256(token: string, secret: string, opts: { now?: number } = {}): { valid: boolean; payload?: Record<string, unknown>; reason?: string } {
|
|
151
|
+
const { createHmac, timingSafeEqual } = nodeCrypto();
|
|
152
|
+
let header: Record<string, unknown>, payload: Record<string, unknown>, parts: string[];
|
|
153
|
+
try {
|
|
154
|
+
parts = token.split('.');
|
|
155
|
+
if (parts.length !== 3) return { valid: false, reason: 'malformed' };
|
|
156
|
+
({ header, payload } = decodeJwt(token));
|
|
157
|
+
} catch {
|
|
158
|
+
return { valid: false, reason: 'malformed' };
|
|
159
|
+
}
|
|
160
|
+
if (header.alg !== 'HS256') return { valid: false, reason: 'unexpected_alg' };
|
|
161
|
+
const expected = base64url(createHmac('sha256', secret).update(`${parts[0]}.${parts[1]}`).digest());
|
|
162
|
+
const a = Buffer.from(parts[2]!, 'utf8');
|
|
163
|
+
const b = Buffer.from(expected, 'utf8');
|
|
164
|
+
if (a.length !== b.length || !timingSafeEqual(a, b)) return { valid: false, reason: 'bad_signature' };
|
|
165
|
+
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
166
|
+
if (typeof payload.exp === 'number' && now >= payload.exp) return { valid: false, reason: 'expired' };
|
|
167
|
+
if (typeof payload.nbf === 'number' && now < payload.nbf) return { valid: false, reason: 'not_yet_valid' };
|
|
168
|
+
return { valid: true, payload };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The decoded parts of a JWT (no verification). */
|
|
172
|
+
export function decodeJwt(token: string): { header: Record<string, unknown>; payload: Record<string, unknown> } {
|
|
173
|
+
const parts = token.split('.');
|
|
174
|
+
if (parts.length !== 3) throw new Error('malformed jwt');
|
|
175
|
+
const dec = (s: string) => JSON.parse(Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')) as Record<string, unknown>;
|
|
176
|
+
return { header: dec(parts[0]!), payload: dec(parts[1]!) };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Verify a token's RS256 signature against a JWKS document (the SAME shape the twin
|
|
181
|
+
* serves), and check exp/nbf. This is what an app does to trust a twin-issued token:
|
|
182
|
+
* `verifyJwtWithJwks(token, fetch('/.well-known/jwks.json'))` succeeds for a valid
|
|
183
|
+
* token, fails for a tampered/expired one. Pure local crypto — no network.
|
|
184
|
+
*/
|
|
185
|
+
export function verifyJwtWithJwks(token: string, jwks: Jwks, opts: { now?: number } = {}): { valid: boolean; payload?: Record<string, unknown>; reason?: string } {
|
|
186
|
+
const { createVerify, createPublicKey } = nodeCrypto();
|
|
187
|
+
let header: Record<string, unknown>, payload: Record<string, unknown>, parts: string[];
|
|
188
|
+
try {
|
|
189
|
+
parts = token.split('.');
|
|
190
|
+
if (parts.length !== 3) return { valid: false, reason: 'malformed' };
|
|
191
|
+
({ header, payload } = decodeJwt(token));
|
|
192
|
+
} catch {
|
|
193
|
+
return { valid: false, reason: 'malformed' };
|
|
194
|
+
}
|
|
195
|
+
if (header.alg !== 'RS256') return { valid: false, reason: 'unexpected_alg' };
|
|
196
|
+
const key = jwks.keys.find((k) => k.kid === header.kid) ?? jwks.keys[0];
|
|
197
|
+
if (!key) return { valid: false, reason: 'no_matching_key' };
|
|
198
|
+
// Reconstruct the public key from the JWK (n, e) — exactly what a real verifier does.
|
|
199
|
+
const publicKey = createPublicKey({ key: { kty: 'RSA', n: key.n, e: key.e }, format: 'jwk' });
|
|
200
|
+
const verifier = createVerify('RSA-SHA256');
|
|
201
|
+
verifier.update(`${parts[0]}.${parts[1]}`);
|
|
202
|
+
verifier.end();
|
|
203
|
+
const sig = Buffer.from(parts[2]!.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
|
|
204
|
+
if (!verifier.verify(publicKey, sig)) return { valid: false, reason: 'bad_signature' };
|
|
205
|
+
const now = opts.now ?? Math.floor(Date.now() / 1000);
|
|
206
|
+
if (typeof payload.exp === 'number' && now >= payload.exp) return { valid: false, reason: 'expired' };
|
|
207
|
+
if (typeof payload.nbf === 'number' && now < payload.nbf) return { valid: false, reason: 'not_yet_valid' };
|
|
208
|
+
return { valid: true, payload };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export type Jwk = { kty: 'RSA'; use: 'sig'; alg: 'RS256'; kid: string; n: string; e: string };
|
|
212
|
+
export type Jwks = { keys: Jwk[] };
|
|
213
|
+
|
|
214
|
+
/** The Clerk-shaped result of authenticating a request (mirrors `RequestState`). */
|
|
215
|
+
export type ClerkAuthState = {
|
|
216
|
+
isSignedIn: boolean;
|
|
217
|
+
userId: string | null;
|
|
218
|
+
sessionId: string | null;
|
|
219
|
+
orgId: string | null;
|
|
220
|
+
claims: Record<string, unknown> | null;
|
|
221
|
+
reason?: string;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Networkless verification of a Clerk session token — what `@clerk/backend`'s
|
|
226
|
+
* `authenticateRequest()` does on the server WITHOUT an extra API round-trip: verify the
|
|
227
|
+
* token's RS256 signature + exp/nbf against a (locally cached) JWKS, then derive the
|
|
228
|
+
* signed-in actor (sub→userId, sid→sessionId, org_id→orgId) from the claims. No network.
|
|
229
|
+
*/
|
|
230
|
+
export function networklessVerifyToken(token: string, jwks: Jwks, opts: { now?: number } = {}): ClerkAuthState {
|
|
231
|
+
const result = verifyJwtWithJwks(token, jwks, opts);
|
|
232
|
+
if (!result.valid || !result.payload) {
|
|
233
|
+
return { isSignedIn: false, userId: null, sessionId: null, orgId: null, claims: null, ...(result.reason ? { reason: result.reason } : {}) };
|
|
234
|
+
}
|
|
235
|
+
const p = result.payload;
|
|
236
|
+
return {
|
|
237
|
+
isSignedIn: true,
|
|
238
|
+
userId: typeof p.sub === 'string' ? p.sub : null,
|
|
239
|
+
sessionId: typeof p.sid === 'string' ? p.sid : null,
|
|
240
|
+
orgId: typeof p.org_id === 'string' ? p.org_id : null,
|
|
241
|
+
claims: p,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Build the JWKS document the twin serves at /.well-known/jwks.json — the real Clerk
|
|
247
|
+
* JWKS shape. The RSA modulus/exponent are exported as base64url JWK members from the
|
|
248
|
+
* SAME persisted key used to sign, so a verifier picks the matching `kid` and trusts
|
|
249
|
+
* twin-issued tokens.
|
|
250
|
+
*/
|
|
251
|
+
export async function buildJwks(root?: string): Promise<Jwks> {
|
|
252
|
+
const { createPublicKey } = nodeCrypto();
|
|
253
|
+
const { kid, publicKeyPem } = await ensureKeypair(root);
|
|
254
|
+
const jwk = createPublicKey(publicKeyPem).export({ format: 'jwk' }) as { n?: string; e?: string };
|
|
255
|
+
return { keys: [{ kty: 'RSA', use: 'sig', alg: 'RS256', kid, n: String(jwk.n), e: String(jwk.e) }] };
|
|
256
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Clerk MIRROR UI — a Clerk-dashboard-like admin view served as a React/TSX app
|
|
2
|
+
// (Bun-bundled, the repo convention). It renders by consuming the twin's OWN REST API on
|
|
3
|
+
// the same origin (/v1/<collection>) — the same endpoints an app uses. Per-vendor + concrete.
|
|
4
|
+
//
|
|
5
|
+
// PURE FRONTEND (R3): the mirror imports no handler and no twin internals — it MOUNTS the
|
|
6
|
+
// pack's OWN fetch adapter (createClerkTwinFetch) as its API backend and reads every byte of
|
|
7
|
+
// state back over the wire, exactly as the browser client does.
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { bundleClient, fileResponse } from '@volter/world-core';
|
|
10
|
+
import { serveHttp } from '@volter/world-core';
|
|
11
|
+
import { createClerkTwinFetch } from './clerk-server.ts';
|
|
12
|
+
|
|
13
|
+
// Resolved LAZILY (on first serve), not at module load: `new URL(..., import.meta.url)` at
|
|
14
|
+
// module scope evaluates at import time, which throws in a bundled/serverless context where
|
|
15
|
+
// `import.meta.url` is not a valid file base (e.g. workerd) even though the mirror UI is never
|
|
16
|
+
// served there. Computing them on demand keeps Node/Bun behavior identical while letting a
|
|
17
|
+
// serverless host import this module's barrel without a startup crash.
|
|
18
|
+
const clientEntryPath = () => new URL('../client/clerk-mirror.tsx', import.meta.url).pathname;
|
|
19
|
+
const clientCssPath = () => new URL('../client/clerk-mirror.css', import.meta.url).pathname;
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Pure, dependency-free render/format helpers. Framework-agnostic (plain data in →
|
|
23
|
+
// plain data out) so they can be unit-tested AND imported by the React/TSX client
|
|
24
|
+
// (Bun tree-shakes the server-only exports below out of the browser bundle). Keep them
|
|
25
|
+
// free of any `@volter/world-core` / `Bun` / fetch-adapter usage.
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
export type ClerkRow = Record<string, any>;
|
|
29
|
+
|
|
30
|
+
/** A user's display name: "First Last", else username, else primary email, else id. */
|
|
31
|
+
export function formatUserName(u: ClerkRow): string {
|
|
32
|
+
const name = [u?.first_name, u?.last_name].filter(Boolean).join(' ').trim();
|
|
33
|
+
if (name) return name;
|
|
34
|
+
if (u?.username) return String(u.username);
|
|
35
|
+
const email = primaryEmail(u);
|
|
36
|
+
return email || String(u?.id ?? '—');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The user's primary email address, resolving primary_email_address_id against the list. */
|
|
40
|
+
export function primaryEmail(u: ClerkRow): string {
|
|
41
|
+
const list = Array.isArray(u?.email_addresses) ? u.email_addresses : [];
|
|
42
|
+
const primaryId = u?.primary_email_address_id;
|
|
43
|
+
const found = list.find((e: any) => e?.id === primaryId) ?? list[0];
|
|
44
|
+
if (found?.email_address) return String(found.email_address);
|
|
45
|
+
if (u?.primary_email) return String(u.primary_email);
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** A short status label for a user (banned/locked override active). */
|
|
50
|
+
export function userStatus(u: ClerkRow): string {
|
|
51
|
+
if (u?.banned) return 'banned';
|
|
52
|
+
if (u?.locked) return 'locked';
|
|
53
|
+
return 'active';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Format a Clerk epoch-millis timestamp as a short date, or an em dash. */
|
|
57
|
+
export function formatClerkDate(ms: unknown): string {
|
|
58
|
+
if (typeof ms !== 'number' || !Number.isFinite(ms) || ms <= 0) return '—';
|
|
59
|
+
try {
|
|
60
|
+
return new Date(ms).toISOString().slice(0, 10);
|
|
61
|
+
} catch {
|
|
62
|
+
return '—';
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** A status → CSS tone class for the status pill (shared design language). */
|
|
67
|
+
export function statusTone(value: string): string {
|
|
68
|
+
const v = value.toLowerCase();
|
|
69
|
+
if (['active', 'verified', 'accepted', 'admin'].includes(v)) return 'ok';
|
|
70
|
+
if (['pending', 'queued', 'locked', 'basic_member'].includes(v)) return 'warn';
|
|
71
|
+
if (['banned', 'revoked', 'expired', 'ended'].includes(v)) return 'bad';
|
|
72
|
+
return 'neutral';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Flatten a (possibly nested) value into label/value lines for the detail panel. */
|
|
76
|
+
export type FlatLine = { key: string; value: string; depth: number };
|
|
77
|
+
export function flattenClerkValue(value: unknown, prefix = '', depth = 0, out: FlatLine[] = []): FlatLine[] {
|
|
78
|
+
if (value === null || value === undefined) {
|
|
79
|
+
out.push({ key: prefix || '(value)', value: '—', depth });
|
|
80
|
+
} else if (Array.isArray(value)) {
|
|
81
|
+
if (value.length === 0) out.push({ key: prefix, value: '[]', depth });
|
|
82
|
+
else value.forEach((v, i) => flattenClerkValue(v, `${prefix}[${i}]`, depth, out));
|
|
83
|
+
} else if (typeof value === 'object') {
|
|
84
|
+
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
|
85
|
+
flattenClerkValue(v, prefix ? `${prefix}.${k}` : k, depth, out);
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
out.push({ key: prefix || '(value)', value: String(value), depth });
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Count members of an org (members_count, with a 0 fallback). */
|
|
94
|
+
export function orgMemberCount(o: ClerkRow): number {
|
|
95
|
+
return typeof o?.members_count === 'number' ? o.members_count : 0;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Server-only (Bun) below — NOT imported by the browser client.
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
|
|
102
|
+
const APP_SHELL = `<!doctype html><html lang="en"><head><meta charset="utf-8" />
|
|
103
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
104
|
+
<base href="/"><title>Clerk twin — admin mirror</title>
|
|
105
|
+
<link rel="stylesheet" href="assets/styles.css" /></head>
|
|
106
|
+
<body><div id="root"></div><script type="module" src="assets/app.js"></script></body></html>`;
|
|
107
|
+
|
|
108
|
+
// cache: the built client bundle, memoized per process — a build artifact, never twin state (the tree
|
|
109
|
+
// holds what this UI renders); cleared on a failed build so the next request rebuilds.
|
|
110
|
+
let clientBundle: Promise<string> | null = null;
|
|
111
|
+
/** Build the React/TSX admin client to browser JS (Bun bundles TSX); cached. */
|
|
112
|
+
export function buildClerkMirrorClient(): Promise<string> {
|
|
113
|
+
if (!clientBundle) {
|
|
114
|
+
clientBundle = bundleClient(clientEntryPath())
|
|
115
|
+
.catch((error) => { clientBundle = null; throw error; });
|
|
116
|
+
}
|
|
117
|
+
return clientBundle;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Serve the Clerk admin mirror UI (React app) + its backing REST API. */
|
|
121
|
+
export async function createClerkMirrorServer(options: { root?: string; port?: number }): Promise<{ port: number; stop: () => void }> {
|
|
122
|
+
const twin = createClerkTwinFetch({ ...(options.root !== undefined ? { root: options.root } : {}) });
|
|
123
|
+
const server = await serveHttp({
|
|
124
|
+
// LOOPBACK-SPECIFIC bind (2026-08-20, the roving ui-verify flake): with the default
|
|
125
|
+
// wildcard hostname, `port: 0` can be handed a port some long-running app already LISTENS
|
|
126
|
+
// on at 127.0.0.1 (SO_REUSEADDR allows the overlapping non-identical bind), and the more
|
|
127
|
+
// specific loopback listener then shadows this server for every 127.0.0.1 fetch — the
|
|
128
|
+
// verify talks to a STRANGER (captured: a desktop app's asset server answering 404s on the
|
|
129
|
+
// mirror's port). Binding 127.0.0.1 makes the kernel allocate a port that is actually free
|
|
130
|
+
// on loopback, so the verify's fetches deterministically reach THIS server.
|
|
131
|
+
hostname: '127.0.0.1',
|
|
132
|
+
port: options.port ?? 0,
|
|
133
|
+
idleTimeout: 60,
|
|
134
|
+
async fetch(request) {
|
|
135
|
+
const url = new URL(request.url);
|
|
136
|
+
if (request.method === 'GET' && url.pathname === '/assets/app.js') {
|
|
137
|
+
try { return new Response(await buildClerkMirrorClient(), { headers: { 'content-type': 'text/javascript; charset=utf-8' } }); }
|
|
138
|
+
catch (error) { return new Response(String(error), { status: 500 }); }
|
|
139
|
+
}
|
|
140
|
+
if (request.method === 'GET' && url.pathname === '/assets/styles.css') {
|
|
141
|
+
return fileResponse(clientCssPath(), { headers: { 'content-type': 'text/css; charset=utf-8' } });
|
|
142
|
+
}
|
|
143
|
+
if (request.method === 'GET' && (url.pathname === '/' || url.pathname === '')) {
|
|
144
|
+
return new Response(APP_SHELL, { headers: { 'content-type': 'text/html; charset=utf-8' } });
|
|
145
|
+
}
|
|
146
|
+
// everything else → the twin's OWN FETCH ADAPTER (composition, R2): the Backend API the
|
|
147
|
+
// React client calls (/v1/<collection>), the Frontend API collections, the vendored
|
|
148
|
+
// clerk-js loader path and the uniform door (GET /twin) all come from the SAME closure
|
|
149
|
+
// `createClerkTwinServer` serves, so the mirror port cannot drift from the API port —
|
|
150
|
+
// one serving code path, one truth.
|
|
151
|
+
return twin(request);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** The app-shell HTML (pure, for tests). The dashboard itself is the React client. */
|
|
158
|
+
export function clerkMirrorHtml(): string {
|
|
159
|
+
return APP_SHELL;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The mirror's stylesheet, for a host serving the shell's `assets/styles.css` itself (the hosted mirror mount). */
|
|
163
|
+
export function clerkMirrorStyles(): Promise<string> {
|
|
164
|
+
return readFile(clientCssPath(), 'utf8');
|
|
165
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
// Clerk twin HTTP server — ONE host serving both real Clerk surfaces:
|
|
2
|
+
// - the Backend API (api.clerk.com shape) for `@clerk/backend`, plus /.well-known/jwks.json
|
|
3
|
+
// - the Frontend API (clerk.<domain> shape) for the REAL `@clerk/clerk-js` browser bundle,
|
|
4
|
+
// which this server also serves at the /npm/@clerk/clerk-js loader path (vendored, pinned)
|
|
5
|
+
// The surfaces project the same row store (clerk-fapi delegates every mutation to the
|
|
6
|
+
// Backend handler), so a user created through either door signs in through the other.
|
|
7
|
+
// Personas are world state: pass `personas` (a JSON file of users with passwords) and the
|
|
8
|
+
// server seeds them as ordinary user rows at start — sign-in is a protocol, never a default.
|
|
9
|
+
// JSON bodies pass through to the handler. Writable by default; readOnly rejects writes (R4).
|
|
10
|
+
import { serveHttp } from '@volter/world-core';
|
|
11
|
+
import { handleClerkTwinRequest } from './clerk-twin.ts';
|
|
12
|
+
import { worldNow, statefulTwinManifest, packAsset } from '@volter/world-core';
|
|
13
|
+
import { handleClerkFapiRequest } from './clerk-fapi.ts';
|
|
14
|
+
|
|
15
|
+
const FAPI_COLLECTIONS = new Set(['environment', 'client', 'dev_browser', 'oauth-consent']);
|
|
16
|
+
|
|
17
|
+
function parseCookies(header: string | null): Record<string, string> {
|
|
18
|
+
if (header === null || header === '') return {};
|
|
19
|
+
return Object.fromEntries(
|
|
20
|
+
header.split(';').map((pair) => {
|
|
21
|
+
const eq = pair.indexOf('=');
|
|
22
|
+
return eq === -1 ? [pair.trim(), ''] : [pair.slice(0, eq).trim(), pair.slice(eq + 1).trim()];
|
|
23
|
+
}),
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Personas arrive as DATA (the cli reads the file — the serve closure reads no host file, R12b). */
|
|
28
|
+
async function seedPersonas(personas: unknown, root?: string): Promise<void> {
|
|
29
|
+
if (!Array.isArray(personas)) throw new Error('clerk personas must be a JSON array of {email, password, …}');
|
|
30
|
+
for (const persona of personas) {
|
|
31
|
+
const email = typeof persona.email === 'string' ? persona.email : undefined;
|
|
32
|
+
const password = typeof persona.password === 'string' ? persona.password : undefined;
|
|
33
|
+
if (email === undefined || password === undefined) throw new Error('each clerk persona needs an email and a password');
|
|
34
|
+
const existing = await handleClerkTwinRequest({ method: 'GET', path: `/v1/users?email_address=${encodeURIComponent(email)}`, ...(root !== undefined ? { root } : {}) });
|
|
35
|
+
if (Array.isArray(existing.body) && existing.body.length > 0) {
|
|
36
|
+
// A persisted user may predate this personas file or carry no password at
|
|
37
|
+
// all (measured live 2026-08-31: a five-day-old persona had
|
|
38
|
+
// password_enabled false, so every tester sign-in failed "incorrect").
|
|
39
|
+
// The personas file is the declared truth for seeded identities: an
|
|
40
|
+
// existing user is RECONCILED, never skipped — the declared password is
|
|
41
|
+
// (re)applied idempotently.
|
|
42
|
+
const user = existing.body[0] as Record<string, unknown>;
|
|
43
|
+
const patched = await handleClerkTwinRequest({
|
|
44
|
+
method: 'PATCH',
|
|
45
|
+
path: `/v1/users/${String(user.id)}`,
|
|
46
|
+
body: JSON.stringify({ password }),
|
|
47
|
+
...(root !== undefined ? { root } : {}),
|
|
48
|
+
});
|
|
49
|
+
if (patched.status < 200 || patched.status >= 300) throw new Error(`failed to reconcile clerk persona "${email}": ${JSON.stringify(patched.body)}`);
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const created = await handleClerkTwinRequest({
|
|
53
|
+
method: 'POST',
|
|
54
|
+
path: '/v1/users',
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
email_address: [email],
|
|
57
|
+
password,
|
|
58
|
+
...(typeof persona.first_name === 'string' ? { first_name: persona.first_name } : {}),
|
|
59
|
+
...(typeof persona.last_name === 'string' ? { last_name: persona.last_name } : {}),
|
|
60
|
+
}),
|
|
61
|
+
...(root !== undefined ? { root } : {}),
|
|
62
|
+
});
|
|
63
|
+
if (created.status < 200 || created.status >= 300) throw new Error(`failed to seed clerk persona "${email}": ${JSON.stringify(created.body)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Options every Clerk-twin HTTP surface needs, independent of who owns the socket. */
|
|
68
|
+
export interface ClerkTwinFetchOptions {
|
|
69
|
+
root?: string;
|
|
70
|
+
readOnly?: boolean;
|
|
71
|
+
personas?: Array<Record<string, unknown>>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The pack's whole HTTP surface as a plain `fetch` — Request in, Response out, no listener.
|
|
76
|
+
*
|
|
77
|
+
* This is the composable form (runtime contract R12b): a Worker / Durable Object entry has NO
|
|
78
|
+
* loopback ports, so it must mount a pack's handler IN-PROCESS. `createClerkTwinServer` is
|
|
79
|
+
* nothing but `Bun.serve` wrapped around this closure, so the standalone (R1) and hosted
|
|
80
|
+
* surfaces are the SAME code — there is no second HTTP adaptation to drift.
|
|
81
|
+
*
|
|
82
|
+
* Personas are seeded ONCE, when the factory is called; the per-request path only awaits that
|
|
83
|
+
* settled promise. A caller that passes no `personas` file seeds nothing.
|
|
84
|
+
*
|
|
85
|
+
* WHAT IT SERVES IS UNCHANGED (R9): GET /twin is built from constants, and the only
|
|
86
|
+
* clock on the path is `worldNow()`, the world's frozen instant.
|
|
87
|
+
*
|
|
88
|
+
* Workerd-clean since R20: the `/npm/@clerk/clerk-js` loader path serves the REAL
|
|
89
|
+
* pinned browser dist through the pack-asset seam (R20), because this pack's whole point is that the
|
|
90
|
+
* unmodified clerk-js bundle loads from it. That route is a local-CDN affordance for a browser
|
|
91
|
+
* lane; the API doors above it touch no filesystem.
|
|
92
|
+
*/
|
|
93
|
+
export function createClerkTwinFetch(options: ClerkTwinFetchOptions = {}): (request: Request) => Promise<Response> {
|
|
94
|
+
const readOnly = options.readOnly ?? false;
|
|
95
|
+
const seeded = options.personas === undefined ? Promise.resolve() : seedPersonas(options.personas, options.root);
|
|
96
|
+
// The vendored dist is resolved LAZILY inside its route: the factory body names no Bun API, so
|
|
97
|
+
// the pack CONSTRUCTS on any host (workerd included) and only the local-CDN route itself needs
|
|
98
|
+
// Bun — until the dist rides a pack-asset seam (`TwinPack.assets`, the pack-asset seam), that route is the
|
|
99
|
+
// pack's one host-bound surface, not the whole pack.
|
|
100
|
+
return async function clerkTwinFetch(request: Request): Promise<Response> {
|
|
101
|
+
await seeded;
|
|
102
|
+
const url = new URL(request.url);
|
|
103
|
+
// GET /twin — the discovery manifest (education inside the twin).
|
|
104
|
+
if (request.method === 'GET' && url.pathname.replace(/\/+$/, '') === '/twin') {
|
|
105
|
+
return Response.json(statefulTwinManifest({ vendor: 'clerk', twinOf: 'the Clerk Backend API', stores: 'users, sessions and organizations' }));
|
|
106
|
+
}
|
|
107
|
+
const corsOrigin = request.headers.get('origin') ?? '*';
|
|
108
|
+
const cors = {
|
|
109
|
+
'access-control-allow-origin': corsOrigin,
|
|
110
|
+
'access-control-allow-credentials': 'true',
|
|
111
|
+
'access-control-allow-headers': 'authorization, content-type',
|
|
112
|
+
'access-control-allow-methods': 'GET, POST, PUT, PATCH, DELETE, OPTIONS',
|
|
113
|
+
};
|
|
114
|
+
if (request.method === 'OPTIONS') return new Response(null, { status: 204, headers: cors });
|
|
115
|
+
// The loader path: the REAL pinned dist, exactly as a CDN would serve it. clerk-js
|
|
116
|
+
// lazy-loads sibling chunk files from the same directory, so every dist file is
|
|
117
|
+
// vendored and served by name; the main bundle is the fallback for the bare path.
|
|
118
|
+
if (request.method === 'GET' && url.pathname.startsWith('/npm/@clerk/clerk-js')) {
|
|
119
|
+
const requested = url.pathname.split('/').at(-1) ?? '';
|
|
120
|
+
const safe = /^[A-Za-z0-9._-]+\.js$/.test(requested) ? requested : 'clerk.browser.js';
|
|
121
|
+
// Through the PACK-ASSET seam (R20): the Bun shell reads this pack's own tree, workerd the
|
|
122
|
+
// assets binding — this route no longer binds the pack to a host (R12b).
|
|
123
|
+
const asset = await packAsset('clerk', `client/clerk-js-dist/${safe}`, 'client/clerk.browser.js');
|
|
124
|
+
if (asset === null) return new Response('clerk-js dist is not shipped with this build', { status: 404, headers: cors });
|
|
125
|
+
return new Response(asset.body, {
|
|
126
|
+
status: 200,
|
|
127
|
+
headers: { 'content-type': 'application/javascript; charset=utf-8', ...cors },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
const seg = url.pathname.split('/').filter((s) => s.length > 0);
|
|
131
|
+
const body = request.method === 'GET' ? '' : await request.text();
|
|
132
|
+
// Frontend API collections dispatch to the FAPI branch; everything else is Backend.
|
|
133
|
+
if (seg[0] === 'v1' && FAPI_COLLECTIONS.has(seg[1] ?? '')) {
|
|
134
|
+
if (readOnly && request.method !== 'GET') {
|
|
135
|
+
return new Response(JSON.stringify({ errors: [{ code: 'read_only', message: 'twin is read-only; omit readOnly to accept writes' }] }), { status: 405, headers: { 'content-type': 'application/json', ...cors } });
|
|
136
|
+
}
|
|
137
|
+
// clerk-js tunnels non-simple verbs through POST + ?_method= to stay preflight-free.
|
|
138
|
+
const methodOverride = url.searchParams.get('_method');
|
|
139
|
+
const { status, body: out, setCookies, html, redirect } = await handleClerkFapiRequest({
|
|
140
|
+
method: request.method === 'POST' && methodOverride !== null ? methodOverride.toUpperCase() : request.method,
|
|
141
|
+
path: url.pathname,
|
|
142
|
+
query: Object.fromEntries(url.searchParams),
|
|
143
|
+
body,
|
|
144
|
+
cookies: parseCookies(request.headers.get('cookie')),
|
|
145
|
+
occurredAt: worldNow(),
|
|
146
|
+
...(options.root !== undefined ? { root: options.root } : {}),
|
|
147
|
+
...(request.headers.get('origin') !== null ? { origin: request.headers.get('origin')! } : {}),
|
|
148
|
+
});
|
|
149
|
+
const headers = new Headers({ 'content-type': html === undefined ? 'application/json' : 'text/html; charset=utf-8', 'clerk-trace-id': 'trace_twin', ...cors });
|
|
150
|
+
for (const cookie of setCookies ?? []) headers.append('set-cookie', cookie);
|
|
151
|
+
if (redirect !== undefined) { headers.set('location', redirect); return new Response(null, { status, headers }); }
|
|
152
|
+
return new Response(html ?? JSON.stringify(out), { status, headers });
|
|
153
|
+
}
|
|
154
|
+
const { status, body: out } = await handleClerkTwinRequest({
|
|
155
|
+
method: request.method,
|
|
156
|
+
path: url.pathname + (url.search || ''),
|
|
157
|
+
body,
|
|
158
|
+
// The origin this request actually arrived on — never a baked host:port (R7).
|
|
159
|
+
host: request.headers.get('x-forwarded-host') ?? request.headers.get('host') ?? url.host,
|
|
160
|
+
readOnly,
|
|
161
|
+
occurredAt: worldNow(),
|
|
162
|
+
...(options.root !== undefined ? { root: options.root } : {}),
|
|
163
|
+
});
|
|
164
|
+
return new Response(JSON.stringify(out), { status, headers: { 'content-type': 'application/json', 'clerk-trace-id': 'trace_twin', ...cors } });
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function createClerkTwinServer(options: { root?: string; port?: number; readOnly?: boolean; personas?: Array<Record<string, unknown>> }): Promise<{ port: number; stop: () => void }> {
|
|
169
|
+
const server = await serveHttp({
|
|
170
|
+
port: options.port ?? 0,
|
|
171
|
+
idleTimeout: 60,
|
|
172
|
+
fetch: createClerkTwinFetch(options),
|
|
173
|
+
});
|
|
174
|
+
return { port: server.port ?? options.port ?? 0, stop: () => server.stop(true) };
|
|
175
|
+
}
|