decane-connect-kit 2.19.0 → 2.21.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/dist/index.js CHANGED
@@ -1946,6 +1946,41 @@ var EventEmitter = class {
1946
1946
  }
1947
1947
  };
1948
1948
 
1949
+ // src/social/auth/redirectResult.ts
1950
+ function readRedirectOutcome(search) {
1951
+ const params = new URLSearchParams(search);
1952
+ const error = params.get("decane_error");
1953
+ if (error) return { kind: "error", error };
1954
+ const jwt = params.get("decane_jwt");
1955
+ if (!jwt) return { kind: "none" };
1956
+ const name = params.get("decane_name");
1957
+ const email = params.get("decane_email");
1958
+ const picture = params.get("decane_picture");
1959
+ const username = params.get("decane_username");
1960
+ const providerSubject = params.get("decane_provider_subject");
1961
+ const profile = {
1962
+ ...name ? { name } : {},
1963
+ ...email ? { email } : {},
1964
+ ...picture ? { picture } : {},
1965
+ ...username ? { username } : {}
1966
+ };
1967
+ return {
1968
+ kind: "result",
1969
+ result: {
1970
+ jwt,
1971
+ userId: params.get("decane_user_id") ?? "",
1972
+ isNewUser: params.get("decane_is_new_user") === "true",
1973
+ hasShare: params.get("decane_has_share") === "true",
1974
+ // Omitted entirely when the provider released nothing, so callers can
1975
+ // test `result.profile` rather than an object that is always present but
1976
+ // sometimes empty. X never sends an email, so this is the normal case
1977
+ // there rather than an edge one.
1978
+ ...Object.keys(profile).length ? { profile } : {},
1979
+ ...providerSubject ? { providerSubject } : {}
1980
+ }
1981
+ };
1982
+ }
1983
+
1949
1984
  // src/social/api.ts
1950
1985
  init_errors();
1951
1986
  var ERROR_MAP = {
@@ -1997,6 +2032,14 @@ var ApiClient = class {
1997
2032
  async getGoogleInitUrl() {
1998
2033
  return this._request("/auth/google/init", { method: "GET" });
1999
2034
  }
2035
+ /**
2036
+ * The X consent URL. Same contract as Google's: the backend holds the client
2037
+ * secret and the PKCE verifier, and answers 503 X_NOT_CONFIGURED when the
2038
+ * deployment has no X credentials.
2039
+ */
2040
+ async getXInitUrl() {
2041
+ return this._request("/auth/x/init", { method: "GET" });
2042
+ }
2000
2043
  /**
2001
2044
  * Exchange a Google ID token the app already holds for a Decane session.
2002
2045
  * Resolves to the SAME identity as the redirect flow, so one Google account
@@ -4296,6 +4339,21 @@ async function createDecaneConnect(config) {
4296
4339
  ...stored.profile ? { profile: stored.profile } : {}
4297
4340
  };
4298
4341
  }
4342
+ async function connectViaRedirect(label, getInitUrl) {
4343
+ const outcome = readRedirectOutcome(window.location.search);
4344
+ if (outcome.kind === "error") {
4345
+ window.history.replaceState({}, "", window.location.pathname);
4346
+ throw new CallSequenceError(`${label} sign-in failed: ${outcome.error}`);
4347
+ }
4348
+ if (outcome.kind === "result") {
4349
+ window.history.replaceState({}, "", window.location.pathname);
4350
+ return handleAuthResult(outcome.result);
4351
+ }
4352
+ const { url } = await getInitUrl();
4353
+ window.location.href = url;
4354
+ return new Promise(() => {
4355
+ });
4356
+ }
4299
4357
  async function handleAuthResult(result) {
4300
4358
  let addresses;
4301
4359
  if (!result.hasShare) {
@@ -4314,7 +4372,8 @@ async function createDecaneConnect(config) {
4314
4372
  return {
4315
4373
  addresses,
4316
4374
  isNewUser: result.isNewUser,
4317
- ...result.profile ? { profile: result.profile } : {}
4375
+ ...result.profile ? { profile: result.profile } : {},
4376
+ ...result.providerSubject ? { providerSubject: result.providerSubject } : {}
4318
4377
  };
4319
4378
  }
4320
4379
  const client = {
@@ -4342,39 +4401,33 @@ async function createDecaneConnect(config) {
4342
4401
  throw new CallSequenceError(`Unknown auth method: ${method}`);
4343
4402
  },
4344
4403
  async connectWithGoogle() {
4345
- const params = new URLSearchParams(window.location.search);
4346
- const decaneJwt = params.get("decane_jwt");
4347
- const decaneError = params.get("decane_error");
4348
- if (decaneError) {
4349
- window.history.replaceState({}, "", window.location.pathname);
4350
- throw new CallSequenceError(`Google sign-in failed: ${decaneError}`);
4351
- }
4352
- if (decaneJwt) {
4353
- const name = params.get("decane_name");
4354
- const email = params.get("decane_email");
4355
- const picture = params.get("decane_picture");
4356
- const profile = {
4357
- ...name ? { name } : {},
4358
- ...email ? { email } : {},
4359
- ...picture ? { picture } : {}
4360
- };
4361
- const result = {
4362
- jwt: decaneJwt,
4363
- userId: params.get("decane_user_id") ?? "",
4364
- isNewUser: params.get("decane_is_new_user") === "true",
4365
- hasShare: params.get("decane_has_share") === "true",
4366
- // Omitted entirely when Google released nothing, so callers can test
4367
- // `result.profile` rather than an object that is always present but
4368
- // sometimes empty.
4369
- ...Object.keys(profile).length ? { profile } : {}
4370
- };
4371
- window.history.replaceState({}, "", window.location.pathname);
4372
- return handleAuthResult(result);
4404
+ return connectViaRedirect("Google", () => api.getGoogleInitUrl());
4405
+ },
4406
+ /**
4407
+ * Finish a redirect sign-in that is already sitting in the URL, whichever
4408
+ * provider started it.
4409
+ *
4410
+ * Google and X bounce back with the same decane_* params, so the page that
4411
+ * receives them cannot tell which one ran — and should not have to. Calling
4412
+ * connectWithGoogle() here would work by accident and then report an X
4413
+ * failure as "Google sign-in failed".
4414
+ *
4415
+ * Throws if there is nothing to finish: unlike the connectWith* methods,
4416
+ * this never starts a flow.
4417
+ */
4418
+ async completeRedirectSignIn() {
4419
+ const outcome = readRedirectOutcome(window.location.search);
4420
+ if (outcome.kind === "none") {
4421
+ throw new CallSequenceError("No sign-in result in the URL to complete");
4373
4422
  }
4374
- const { url } = await api.getGoogleInitUrl();
4375
- window.location.href = url;
4376
- return new Promise(() => {
4377
- });
4423
+ window.history.replaceState({}, "", window.location.pathname);
4424
+ if (outcome.kind === "error") {
4425
+ throw new CallSequenceError(`Sign-in failed: ${outcome.error}`);
4426
+ }
4427
+ return handleAuthResult(outcome.result);
4428
+ },
4429
+ async connectWithX() {
4430
+ return connectViaRedirect("X", () => api.getXInitUrl());
4378
4431
  },
4379
4432
  async connectWithGoogleToken(idToken) {
4380
4433
  if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
@@ -5198,6 +5251,20 @@ function ChainIconStack({ support, size = 16 }) {
5198
5251
  /* @__PURE__ */ React.createElement(ChainCircleIcon, { chain, size })
5199
5252
  )));
5200
5253
  }
5254
+ function XLogo({ size = 14 }) {
5255
+ return /* @__PURE__ */ React.createElement(
5256
+ "svg",
5257
+ {
5258
+ width: size,
5259
+ height: size,
5260
+ viewBox: "0 0 24 24",
5261
+ fill: "currentColor",
5262
+ "aria-hidden": "true",
5263
+ focusable: "false"
5264
+ },
5265
+ /* @__PURE__ */ React.createElement("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" })
5266
+ );
5267
+ }
5201
5268
 
5202
5269
  // src/ui/WalletSelector.tsx
5203
5270
  var FONT_URL = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap";
@@ -5339,6 +5406,7 @@ function WalletSelector({
5339
5406
  socialAuthMethods = ["google", "email"],
5340
5407
  onGoogleSignIn,
5341
5408
  onKingsChatSignIn,
5409
+ onXSignIn,
5342
5410
  onEmailStart,
5343
5411
  onEmailVerify,
5344
5412
  onPhoneStart,
@@ -5433,6 +5501,17 @@ function WalletSelector({
5433
5501
  setSocialLoad(false);
5434
5502
  }
5435
5503
  }
5504
+ async function handleXClick() {
5505
+ if (!onXSignIn) return;
5506
+ setSocialLoad(true);
5507
+ setSocialErr("");
5508
+ try {
5509
+ await onXSignIn();
5510
+ } catch (e) {
5511
+ setSocialErr(e.message);
5512
+ setSocialLoad(false);
5513
+ }
5514
+ }
5436
5515
  async function handleCustomAuthClick() {
5437
5516
  if (!onCustomAuthSignIn) return;
5438
5517
  setSocialLoad(true);
@@ -5914,6 +5993,38 @@ function WalletSelector({
5914
5993
  },
5915
5994
  /* @__PURE__ */ React2.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ React2.createElement("circle", { cx: "9", cy: "9", r: "9", fill: "var(--dc-accent)" }), /* @__PURE__ */ React2.createElement("path", { d: "M5.5 4.5h1.9v3.6l3.1-3.6h2.3l-3.3 3.8 3.5 5.2h-2.3l-2.5-3.8-.8.9v2.9H5.5V4.5z", fill: "var(--dc-accent-text)" })),
5916
5995
  socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
5996
+ ), !passkeyFirst && socialAuthMethods.includes("x") && onXSignIn && /* @__PURE__ */ React2.createElement(
5997
+ "button",
5998
+ {
5999
+ onClick: handleXClick,
6000
+ disabled: socialLoad,
6001
+ style: {
6002
+ display: "flex",
6003
+ alignItems: "center",
6004
+ justifyContent: "center",
6005
+ gap: 10,
6006
+ width: "100%",
6007
+ padding: "11px 16px",
6008
+ background: "var(--dc-surface)",
6009
+ border: "1px solid var(--dc-border)",
6010
+ borderRadius: 10,
6011
+ cursor: socialLoad ? "not-allowed" : "pointer",
6012
+ fontSize: 14,
6013
+ fontWeight: 600,
6014
+ color: "var(--dc-text)",
6015
+ fontFamily: "inherit",
6016
+ transition: "border-color 0.12s, background 0.12s",
6017
+ opacity: socialLoad ? 0.6 : 1
6018
+ },
6019
+ onMouseEnter: (e) => {
6020
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
6021
+ },
6022
+ onMouseLeave: (e) => {
6023
+ e.currentTarget.style.borderColor = "var(--dc-border)";
6024
+ }
6025
+ },
6026
+ /* @__PURE__ */ React2.createElement(XLogo, { size: 16 }),
6027
+ socialLoad ? "Redirecting\u2026" : "Continue with X"
5917
6028
  ), !passkeyFirst && socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ React2.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ React2.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ React2.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), !passkeyFirst && socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ React2.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ React2.createElement(
5918
6029
  "input",
5919
6030
  {
@@ -6281,6 +6392,7 @@ function AddressRow({
6281
6392
  function SignInView({
6282
6393
  onGoogleSignIn,
6283
6394
  onKingsChatSignIn,
6395
+ onXSignIn,
6284
6396
  onEmailStart,
6285
6397
  onEmailVerify,
6286
6398
  onPhoneStart,
@@ -6295,6 +6407,7 @@ function SignInView({
6295
6407
  }) {
6296
6408
  const [googleLoading, setGoogleLoading] = useState2(false);
6297
6409
  const [kingsChatLoading, setKingsChatLoading] = useState2(false);
6410
+ const [xLoading, setXLoading] = useState2(false);
6298
6411
  const [email, setEmail] = useState2("");
6299
6412
  const [code, setCode] = useState2("");
6300
6413
  const [emailSent, setEmailSent] = useState2(false);
@@ -6345,6 +6458,17 @@ function SignInView({
6345
6458
  setKingsChatLoading(false);
6346
6459
  }
6347
6460
  }
6461
+ async function handleX() {
6462
+ if (!onXSignIn) return;
6463
+ setError("");
6464
+ setXLoading(true);
6465
+ try {
6466
+ await onXSignIn();
6467
+ } catch (e) {
6468
+ setError(e.message);
6469
+ setXLoading(false);
6470
+ }
6471
+ }
6348
6472
  async function handleEmailStart(e) {
6349
6473
  e.preventDefault();
6350
6474
  setError("");
@@ -6503,6 +6627,32 @@ function SignInView({
6503
6627
  }
6504
6628
  },
6505
6629
  kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
6630
+ ), !passkeyFirst && authMethods.includes("x") && onXSignIn && /* @__PURE__ */ React3.createElement(
6631
+ "button",
6632
+ {
6633
+ onClick: handleX,
6634
+ disabled: xLoading,
6635
+ style: {
6636
+ width: "100%",
6637
+ padding: "10px 0",
6638
+ marginBottom: 10,
6639
+ background: "var(--dc-surface)",
6640
+ color: "var(--dc-text)",
6641
+ border: "1px solid var(--dc-border)",
6642
+ borderRadius: 10,
6643
+ fontSize: 13,
6644
+ fontWeight: 700,
6645
+ cursor: xLoading ? "not-allowed" : "pointer",
6646
+ opacity: xLoading ? 0.7 : 1,
6647
+ fontFamily: "inherit",
6648
+ display: "flex",
6649
+ alignItems: "center",
6650
+ justifyContent: "center",
6651
+ gap: 8
6652
+ }
6653
+ },
6654
+ /* @__PURE__ */ React3.createElement(XLogo, null),
6655
+ xLoading ? "Redirecting\u2026" : "Continue with X"
6506
6656
  ), !passkeyFirst && authMethods.includes("email") && /* @__PURE__ */ React3.createElement(React3.Fragment, null, authMethods.includes("google") && /* @__PURE__ */ React3.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), !emailSent ? /* @__PURE__ */ React3.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", gap: 8 } }, /* @__PURE__ */ React3.createElement(
6507
6657
  "input",
6508
6658
  {
@@ -7043,6 +7193,7 @@ var DEFAULT_SOCIAL = {
7043
7193
  addresses: null,
7044
7194
  isNewUser: false,
7045
7195
  profile: null,
7196
+ providerSubject: null,
7046
7197
  isUnlocked: false,
7047
7198
  sessionExpiresAt: null,
7048
7199
  needsReconnect: false,
@@ -7165,7 +7316,7 @@ function DecaneKit({ config, children }) {
7165
7316
  });
7166
7317
  const params = new URLSearchParams(window.location.search);
7167
7318
  if (params.has("decane_jwt") || params.has("decane_error")) {
7168
- sdk.connectWithGoogle().then(applyResult).catch((e) => {
7319
+ sdk.completeRedirectSignIn().then(applyResult).catch((e) => {
7169
7320
  setSocialAuth((p) => p.phase ? { ...p, phase: null } : p);
7170
7321
  console.error(e);
7171
7322
  });
@@ -7178,6 +7329,7 @@ function DecaneKit({ config, children }) {
7178
7329
  addresses: result.addresses,
7179
7330
  isNewUser: result.isNewUser,
7180
7331
  profile: result.profile ?? null,
7332
+ providerSubject: result.providerSubject ?? null,
7181
7333
  isUnlocked: sdk?.isUnlocked() ?? false,
7182
7334
  sessionExpiresAt: sdk?.sessionExpiresAt() ?? null,
7183
7335
  needsReconnect: false,
@@ -7206,6 +7358,11 @@ function DecaneKit({ config, children }) {
7206
7358
  if (!sdk) throw new Error("Social sign-in not configured");
7207
7359
  await sdk.connectWithGoogle();
7208
7360
  }, []);
7361
+ const connectWithX = useCallback3(async () => {
7362
+ const sdk = socialRef.current;
7363
+ if (!sdk) throw new Error("Social sign-in not configured");
7364
+ await sdk.connectWithX();
7365
+ }, []);
7209
7366
  const connectWithKingsChat = useCallback3(async () => {
7210
7367
  const sdk = socialRef.current;
7211
7368
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -7426,6 +7583,7 @@ function DecaneKit({ config, children }) {
7426
7583
  signInWithPasskey,
7427
7584
  connectWithGoogle,
7428
7585
  connectWithKingsChat,
7586
+ connectWithX,
7429
7587
  connectWithEmail,
7430
7588
  verifyEmailCode: verifyEmailCode2,
7431
7589
  connectWithPhone,
@@ -7477,6 +7635,7 @@ function DecaneKit({ config, children }) {
7477
7635
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
7478
7636
  onGoogleSignIn: connectWithGoogle,
7479
7637
  onKingsChatSignIn: connectWithKingsChat,
7638
+ onXSignIn: connectWithX,
7480
7639
  onEmailStart: connectWithEmail,
7481
7640
  onEmailVerify: verifyEmailCode2,
7482
7641
  onPhoneStart: connectWithPhone,
@@ -7501,6 +7660,7 @@ function DecaneKit({ config, children }) {
7501
7660
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
7502
7661
  onGoogleSignIn: connectWithGoogle,
7503
7662
  onKingsChatSignIn: connectWithKingsChat,
7663
+ onXSignIn: connectWithX,
7504
7664
  onEmailStart: connectWithEmail,
7505
7665
  onEmailVerify: verifyEmailCode2,
7506
7666
  onPhoneStart: connectWithPhone,
@@ -7535,6 +7695,7 @@ function useSocialAuth() {
7535
7695
  socialAuth,
7536
7696
  connectWithGoogle,
7537
7697
  connectWithKingsChat,
7698
+ connectWithX,
7538
7699
  connectWithEmail,
7539
7700
  verifyEmailCode: verifyEmailCode2,
7540
7701
  connectWithPhone,
@@ -7548,6 +7709,7 @@ function useSocialAuth() {
7548
7709
  } = useDecaneContext();
7549
7710
  const [googleLoading, setGoogleLoading] = useState4(false);
7550
7711
  const [kingschatLoading, setKingschatLoading] = useState4(false);
7712
+ const [xLoading, setXLoading] = useState4(false);
7551
7713
  const [emailLoading, setEmailLoading] = useState4(false);
7552
7714
  const [phoneLoading, setPhoneLoading] = useState4(false);
7553
7715
  const [tokenLoading, setTokenLoading] = useState4(false);
@@ -7563,6 +7725,16 @@ function useSocialAuth() {
7563
7725
  setGoogleLoading(false);
7564
7726
  }
7565
7727
  }, [connectWithGoogle]);
7728
+ const signInWithX = useCallback4(async () => {
7729
+ setXLoading(true);
7730
+ setError(null);
7731
+ try {
7732
+ await connectWithX();
7733
+ } catch (e) {
7734
+ setError(e.message);
7735
+ setXLoading(false);
7736
+ }
7737
+ }, [connectWithX]);
7566
7738
  const signInWithKingsChat = useCallback4(async () => {
7567
7739
  setKingschatLoading(true);
7568
7740
  setError(null);
@@ -7690,6 +7862,9 @@ function useSocialAuth() {
7690
7862
  // user first signed in, so it carries the same name and email a Google
7691
7863
  // sign-in does rather than coming back anonymous.
7692
7864
  profile: socialAuth.profile,
7865
+ // The provider's stable id, where it gives one. Null otherwise. Safe to
7866
+ // match an account on, unlike the handle in `profile.username`.
7867
+ providerSubject: socialAuth.providerSubject,
7693
7868
  // Long-running work in progress, for driving your own status UI.
7694
7869
  phase: socialAuth.phase,
7695
7870
  isCreatingWallet: socialAuth.phase === "creating",
@@ -7700,6 +7875,7 @@ function useSocialAuth() {
7700
7875
  needsReconnect: socialAuth.needsReconnect,
7701
7876
  googleLoading,
7702
7877
  kingschatLoading,
7878
+ xLoading,
7703
7879
  emailLoading,
7704
7880
  phoneLoading,
7705
7881
  tokenLoading,
@@ -7709,6 +7885,7 @@ function useSocialAuth() {
7709
7885
  clearError: () => setError(null),
7710
7886
  signInWithGoogle,
7711
7887
  signInWithKingsChat,
7888
+ signInWithX,
7712
7889
  sendEmailCode,
7713
7890
  confirmEmailCode,
7714
7891
  sendPhoneCode,