decane-connect-kit 2.19.0 → 2.20.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.cjs CHANGED
@@ -2028,6 +2028,37 @@ var EventEmitter = class {
2028
2028
  }
2029
2029
  };
2030
2030
 
2031
+ // src/social/auth/redirectResult.ts
2032
+ function readRedirectOutcome(search) {
2033
+ const params = new URLSearchParams(search);
2034
+ const error = params.get("decane_error");
2035
+ if (error) return { kind: "error", error };
2036
+ const jwt = params.get("decane_jwt");
2037
+ if (!jwt) return { kind: "none" };
2038
+ const name = params.get("decane_name");
2039
+ const email = params.get("decane_email");
2040
+ const picture = params.get("decane_picture");
2041
+ const profile = {
2042
+ ...name ? { name } : {},
2043
+ ...email ? { email } : {},
2044
+ ...picture ? { picture } : {}
2045
+ };
2046
+ return {
2047
+ kind: "result",
2048
+ result: {
2049
+ jwt,
2050
+ userId: params.get("decane_user_id") ?? "",
2051
+ isNewUser: params.get("decane_is_new_user") === "true",
2052
+ hasShare: params.get("decane_has_share") === "true",
2053
+ // Omitted entirely when the provider released nothing, so callers can
2054
+ // test `result.profile` rather than an object that is always present but
2055
+ // sometimes empty. X never sends an email, so this is the normal case
2056
+ // there rather than an edge one.
2057
+ ...Object.keys(profile).length ? { profile } : {}
2058
+ }
2059
+ };
2060
+ }
2061
+
2031
2062
  // src/social/api.ts
2032
2063
  init_errors();
2033
2064
  var ERROR_MAP = {
@@ -2079,6 +2110,14 @@ var ApiClient = class {
2079
2110
  async getGoogleInitUrl() {
2080
2111
  return this._request("/auth/google/init", { method: "GET" });
2081
2112
  }
2113
+ /**
2114
+ * The X consent URL. Same contract as Google's: the backend holds the client
2115
+ * secret and the PKCE verifier, and answers 503 X_NOT_CONFIGURED when the
2116
+ * deployment has no X credentials.
2117
+ */
2118
+ async getXInitUrl() {
2119
+ return this._request("/auth/x/init", { method: "GET" });
2120
+ }
2082
2121
  /**
2083
2122
  * Exchange a Google ID token the app already holds for a Decane session.
2084
2123
  * Resolves to the SAME identity as the redirect flow, so one Google account
@@ -4378,6 +4417,21 @@ async function createDecaneConnect(config) {
4378
4417
  ...stored.profile ? { profile: stored.profile } : {}
4379
4418
  };
4380
4419
  }
4420
+ async function connectViaRedirect(label, getInitUrl) {
4421
+ const outcome = readRedirectOutcome(window.location.search);
4422
+ if (outcome.kind === "error") {
4423
+ window.history.replaceState({}, "", window.location.pathname);
4424
+ throw new CallSequenceError(`${label} sign-in failed: ${outcome.error}`);
4425
+ }
4426
+ if (outcome.kind === "result") {
4427
+ window.history.replaceState({}, "", window.location.pathname);
4428
+ return handleAuthResult(outcome.result);
4429
+ }
4430
+ const { url } = await getInitUrl();
4431
+ window.location.href = url;
4432
+ return new Promise(() => {
4433
+ });
4434
+ }
4381
4435
  async function handleAuthResult(result) {
4382
4436
  let addresses;
4383
4437
  if (!result.hasShare) {
@@ -4424,39 +4478,33 @@ async function createDecaneConnect(config) {
4424
4478
  throw new CallSequenceError(`Unknown auth method: ${method}`);
4425
4479
  },
4426
4480
  async connectWithGoogle() {
4427
- const params = new URLSearchParams(window.location.search);
4428
- const decaneJwt = params.get("decane_jwt");
4429
- const decaneError = params.get("decane_error");
4430
- if (decaneError) {
4431
- window.history.replaceState({}, "", window.location.pathname);
4432
- throw new CallSequenceError(`Google sign-in failed: ${decaneError}`);
4433
- }
4434
- if (decaneJwt) {
4435
- const name = params.get("decane_name");
4436
- const email = params.get("decane_email");
4437
- const picture = params.get("decane_picture");
4438
- const profile = {
4439
- ...name ? { name } : {},
4440
- ...email ? { email } : {},
4441
- ...picture ? { picture } : {}
4442
- };
4443
- const result = {
4444
- jwt: decaneJwt,
4445
- userId: params.get("decane_user_id") ?? "",
4446
- isNewUser: params.get("decane_is_new_user") === "true",
4447
- hasShare: params.get("decane_has_share") === "true",
4448
- // Omitted entirely when Google released nothing, so callers can test
4449
- // `result.profile` rather than an object that is always present but
4450
- // sometimes empty.
4451
- ...Object.keys(profile).length ? { profile } : {}
4452
- };
4453
- window.history.replaceState({}, "", window.location.pathname);
4454
- return handleAuthResult(result);
4481
+ return connectViaRedirect("Google", () => api.getGoogleInitUrl());
4482
+ },
4483
+ /**
4484
+ * Finish a redirect sign-in that is already sitting in the URL, whichever
4485
+ * provider started it.
4486
+ *
4487
+ * Google and X bounce back with the same decane_* params, so the page that
4488
+ * receives them cannot tell which one ran — and should not have to. Calling
4489
+ * connectWithGoogle() here would work by accident and then report an X
4490
+ * failure as "Google sign-in failed".
4491
+ *
4492
+ * Throws if there is nothing to finish: unlike the connectWith* methods,
4493
+ * this never starts a flow.
4494
+ */
4495
+ async completeRedirectSignIn() {
4496
+ const outcome = readRedirectOutcome(window.location.search);
4497
+ if (outcome.kind === "none") {
4498
+ throw new CallSequenceError("No sign-in result in the URL to complete");
4455
4499
  }
4456
- const { url } = await api.getGoogleInitUrl();
4457
- window.location.href = url;
4458
- return new Promise(() => {
4459
- });
4500
+ window.history.replaceState({}, "", window.location.pathname);
4501
+ if (outcome.kind === "error") {
4502
+ throw new CallSequenceError(`Sign-in failed: ${outcome.error}`);
4503
+ }
4504
+ return handleAuthResult(outcome.result);
4505
+ },
4506
+ async connectWithX() {
4507
+ return connectViaRedirect("X", () => api.getXInitUrl());
4460
4508
  },
4461
4509
  async connectWithGoogleToken(idToken) {
4462
4510
  if (!idToken) throw new CallSequenceError("connectWithGoogleToken requires an ID token");
@@ -5280,6 +5328,20 @@ function ChainIconStack({ support, size = 16 }) {
5280
5328
  /* @__PURE__ */ import_react.default.createElement(ChainCircleIcon, { chain, size })
5281
5329
  )));
5282
5330
  }
5331
+ function XLogo({ size = 14 }) {
5332
+ return /* @__PURE__ */ import_react.default.createElement(
5333
+ "svg",
5334
+ {
5335
+ width: size,
5336
+ height: size,
5337
+ viewBox: "0 0 24 24",
5338
+ fill: "currentColor",
5339
+ "aria-hidden": "true",
5340
+ focusable: "false"
5341
+ },
5342
+ /* @__PURE__ */ import_react.default.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" })
5343
+ );
5344
+ }
5283
5345
 
5284
5346
  // src/ui/WalletSelector.tsx
5285
5347
  var FONT_URL = "https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap";
@@ -5421,6 +5483,7 @@ function WalletSelector({
5421
5483
  socialAuthMethods = ["google", "email"],
5422
5484
  onGoogleSignIn,
5423
5485
  onKingsChatSignIn,
5486
+ onXSignIn,
5424
5487
  onEmailStart,
5425
5488
  onEmailVerify,
5426
5489
  onPhoneStart,
@@ -5515,6 +5578,17 @@ function WalletSelector({
5515
5578
  setSocialLoad(false);
5516
5579
  }
5517
5580
  }
5581
+ async function handleXClick() {
5582
+ if (!onXSignIn) return;
5583
+ setSocialLoad(true);
5584
+ setSocialErr("");
5585
+ try {
5586
+ await onXSignIn();
5587
+ } catch (e) {
5588
+ setSocialErr(e.message);
5589
+ setSocialLoad(false);
5590
+ }
5591
+ }
5518
5592
  async function handleCustomAuthClick() {
5519
5593
  if (!onCustomAuthSignIn) return;
5520
5594
  setSocialLoad(true);
@@ -5996,6 +6070,38 @@ function WalletSelector({
5996
6070
  },
5997
6071
  /* @__PURE__ */ import_react2.default.createElement("svg", { width: "18", height: "18", viewBox: "0 0 18 18", fill: "none", "aria-hidden": "true" }, /* @__PURE__ */ import_react2.default.createElement("circle", { cx: "9", cy: "9", r: "9", fill: "var(--dc-accent)" }), /* @__PURE__ */ import_react2.default.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)" })),
5998
6072
  socialLoad ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
6073
+ ), !passkeyFirst && socialAuthMethods.includes("x") && onXSignIn && /* @__PURE__ */ import_react2.default.createElement(
6074
+ "button",
6075
+ {
6076
+ onClick: handleXClick,
6077
+ disabled: socialLoad,
6078
+ style: {
6079
+ display: "flex",
6080
+ alignItems: "center",
6081
+ justifyContent: "center",
6082
+ gap: 10,
6083
+ width: "100%",
6084
+ padding: "11px 16px",
6085
+ background: "var(--dc-surface)",
6086
+ border: "1px solid var(--dc-border)",
6087
+ borderRadius: 10,
6088
+ cursor: socialLoad ? "not-allowed" : "pointer",
6089
+ fontSize: 14,
6090
+ fontWeight: 600,
6091
+ color: "var(--dc-text)",
6092
+ fontFamily: "inherit",
6093
+ transition: "border-color 0.12s, background 0.12s",
6094
+ opacity: socialLoad ? 0.6 : 1
6095
+ },
6096
+ onMouseEnter: (e) => {
6097
+ if (!socialLoad) e.currentTarget.style.borderColor = "var(--dc-border-accent)";
6098
+ },
6099
+ onMouseLeave: (e) => {
6100
+ e.currentTarget.style.borderColor = "var(--dc-border)";
6101
+ }
6102
+ },
6103
+ /* @__PURE__ */ import_react2.default.createElement(XLogo, { size: 16 }),
6104
+ socialLoad ? "Redirecting\u2026" : "Continue with X"
5999
6105
  ), !passkeyFirst && socialAuthMethods.includes("google") && socialAuthMethods.includes("email") && /* @__PURE__ */ import_react2.default.createElement("div", { style: { display: "flex", alignItems: "center", gap: 10 } }, /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } }), /* @__PURE__ */ import_react2.default.createElement("span", { style: { fontSize: 11, color: "var(--dc-text-muted)", whiteSpace: "nowrap" } }, "or continue with email"), /* @__PURE__ */ import_react2.default.createElement("div", { style: { flex: 1, height: 1, background: "var(--dc-border)" } })), !passkeyFirst && socialAuthMethods.includes("email") && (emailStep === "input" ? /* @__PURE__ */ import_react2.default.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", flexDirection: "column", gap: 8 } }, /* @__PURE__ */ import_react2.default.createElement(
6000
6106
  "input",
6001
6107
  {
@@ -6363,6 +6469,7 @@ function AddressRow({
6363
6469
  function SignInView({
6364
6470
  onGoogleSignIn,
6365
6471
  onKingsChatSignIn,
6472
+ onXSignIn,
6366
6473
  onEmailStart,
6367
6474
  onEmailVerify,
6368
6475
  onPhoneStart,
@@ -6377,6 +6484,7 @@ function SignInView({
6377
6484
  }) {
6378
6485
  const [googleLoading, setGoogleLoading] = (0, import_react3.useState)(false);
6379
6486
  const [kingsChatLoading, setKingsChatLoading] = (0, import_react3.useState)(false);
6487
+ const [xLoading, setXLoading] = (0, import_react3.useState)(false);
6380
6488
  const [email, setEmail] = (0, import_react3.useState)("");
6381
6489
  const [code, setCode] = (0, import_react3.useState)("");
6382
6490
  const [emailSent, setEmailSent] = (0, import_react3.useState)(false);
@@ -6427,6 +6535,17 @@ function SignInView({
6427
6535
  setKingsChatLoading(false);
6428
6536
  }
6429
6537
  }
6538
+ async function handleX() {
6539
+ if (!onXSignIn) return;
6540
+ setError("");
6541
+ setXLoading(true);
6542
+ try {
6543
+ await onXSignIn();
6544
+ } catch (e) {
6545
+ setError(e.message);
6546
+ setXLoading(false);
6547
+ }
6548
+ }
6430
6549
  async function handleEmailStart(e) {
6431
6550
  e.preventDefault();
6432
6551
  setError("");
@@ -6585,6 +6704,32 @@ function SignInView({
6585
6704
  }
6586
6705
  },
6587
6706
  kingsChatLoading ? "Waiting for KingsChat\u2026" : "Continue with KingsChat"
6707
+ ), !passkeyFirst && authMethods.includes("x") && onXSignIn && /* @__PURE__ */ import_react3.default.createElement(
6708
+ "button",
6709
+ {
6710
+ onClick: handleX,
6711
+ disabled: xLoading,
6712
+ style: {
6713
+ width: "100%",
6714
+ padding: "10px 0",
6715
+ marginBottom: 10,
6716
+ background: "var(--dc-surface)",
6717
+ color: "var(--dc-text)",
6718
+ border: "1px solid var(--dc-border)",
6719
+ borderRadius: 10,
6720
+ fontSize: 13,
6721
+ fontWeight: 700,
6722
+ cursor: xLoading ? "not-allowed" : "pointer",
6723
+ opacity: xLoading ? 0.7 : 1,
6724
+ fontFamily: "inherit",
6725
+ display: "flex",
6726
+ alignItems: "center",
6727
+ justifyContent: "center",
6728
+ gap: 8
6729
+ }
6730
+ },
6731
+ /* @__PURE__ */ import_react3.default.createElement(XLogo, null),
6732
+ xLoading ? "Redirecting\u2026" : "Continue with X"
6588
6733
  ), !passkeyFirst && authMethods.includes("email") && /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, authMethods.includes("google") && /* @__PURE__ */ import_react3.default.createElement("div", { style: { textAlign: "center", color: "var(--dc-text-muted)", fontSize: 12, margin: "10px 0" } }, "\u2014 or \u2014"), !emailSent ? /* @__PURE__ */ import_react3.default.createElement("form", { onSubmit: handleEmailStart, style: { display: "flex", gap: 8 } }, /* @__PURE__ */ import_react3.default.createElement(
6589
6734
  "input",
6590
6735
  {
@@ -7240,7 +7385,7 @@ function DecaneKit({ config, children }) {
7240
7385
  });
7241
7386
  const params = new URLSearchParams(window.location.search);
7242
7387
  if (params.has("decane_jwt") || params.has("decane_error")) {
7243
- sdk.connectWithGoogle().then(applyResult).catch((e) => {
7388
+ sdk.completeRedirectSignIn().then(applyResult).catch((e) => {
7244
7389
  setSocialAuth((p) => p.phase ? { ...p, phase: null } : p);
7245
7390
  console.error(e);
7246
7391
  });
@@ -7281,6 +7426,11 @@ function DecaneKit({ config, children }) {
7281
7426
  if (!sdk) throw new Error("Social sign-in not configured");
7282
7427
  await sdk.connectWithGoogle();
7283
7428
  }, []);
7429
+ const connectWithX = (0, import_react5.useCallback)(async () => {
7430
+ const sdk = socialRef.current;
7431
+ if (!sdk) throw new Error("Social sign-in not configured");
7432
+ await sdk.connectWithX();
7433
+ }, []);
7284
7434
  const connectWithKingsChat = (0, import_react5.useCallback)(async () => {
7285
7435
  const sdk = socialRef.current;
7286
7436
  if (!sdk) throw new Error("Social sign-in not configured");
@@ -7501,6 +7651,7 @@ function DecaneKit({ config, children }) {
7501
7651
  signInWithPasskey,
7502
7652
  connectWithGoogle,
7503
7653
  connectWithKingsChat,
7654
+ connectWithX,
7504
7655
  connectWithEmail,
7505
7656
  verifyEmailCode: verifyEmailCode2,
7506
7657
  connectWithPhone,
@@ -7552,6 +7703,7 @@ function DecaneKit({ config, children }) {
7552
7703
  socialAuthMethods: config?.social?.authMethods ?? ["google", "email"],
7553
7704
  onGoogleSignIn: connectWithGoogle,
7554
7705
  onKingsChatSignIn: connectWithKingsChat,
7706
+ onXSignIn: connectWithX,
7555
7707
  onEmailStart: connectWithEmail,
7556
7708
  onEmailVerify: verifyEmailCode2,
7557
7709
  onPhoneStart: connectWithPhone,
@@ -7576,6 +7728,7 @@ function DecaneKit({ config, children }) {
7576
7728
  onCustomAuthSignIn: customAuthConfig ? connectWithCustomAuth : void 0,
7577
7729
  onGoogleSignIn: connectWithGoogle,
7578
7730
  onKingsChatSignIn: connectWithKingsChat,
7731
+ onXSignIn: connectWithX,
7579
7732
  onEmailStart: connectWithEmail,
7580
7733
  onEmailVerify: verifyEmailCode2,
7581
7734
  onPhoneStart: connectWithPhone,
@@ -7610,6 +7763,7 @@ function useSocialAuth() {
7610
7763
  socialAuth,
7611
7764
  connectWithGoogle,
7612
7765
  connectWithKingsChat,
7766
+ connectWithX,
7613
7767
  connectWithEmail,
7614
7768
  verifyEmailCode: verifyEmailCode2,
7615
7769
  connectWithPhone,
@@ -7623,6 +7777,7 @@ function useSocialAuth() {
7623
7777
  } = useDecaneContext();
7624
7778
  const [googleLoading, setGoogleLoading] = (0, import_react6.useState)(false);
7625
7779
  const [kingschatLoading, setKingschatLoading] = (0, import_react6.useState)(false);
7780
+ const [xLoading, setXLoading] = (0, import_react6.useState)(false);
7626
7781
  const [emailLoading, setEmailLoading] = (0, import_react6.useState)(false);
7627
7782
  const [phoneLoading, setPhoneLoading] = (0, import_react6.useState)(false);
7628
7783
  const [tokenLoading, setTokenLoading] = (0, import_react6.useState)(false);
@@ -7638,6 +7793,16 @@ function useSocialAuth() {
7638
7793
  setGoogleLoading(false);
7639
7794
  }
7640
7795
  }, [connectWithGoogle]);
7796
+ const signInWithX = (0, import_react6.useCallback)(async () => {
7797
+ setXLoading(true);
7798
+ setError(null);
7799
+ try {
7800
+ await connectWithX();
7801
+ } catch (e) {
7802
+ setError(e.message);
7803
+ setXLoading(false);
7804
+ }
7805
+ }, [connectWithX]);
7641
7806
  const signInWithKingsChat = (0, import_react6.useCallback)(async () => {
7642
7807
  setKingschatLoading(true);
7643
7808
  setError(null);
@@ -7775,6 +7940,7 @@ function useSocialAuth() {
7775
7940
  needsReconnect: socialAuth.needsReconnect,
7776
7941
  googleLoading,
7777
7942
  kingschatLoading,
7943
+ xLoading,
7778
7944
  emailLoading,
7779
7945
  phoneLoading,
7780
7946
  tokenLoading,
@@ -7784,6 +7950,7 @@ function useSocialAuth() {
7784
7950
  clearError: () => setError(null),
7785
7951
  signInWithGoogle,
7786
7952
  signInWithKingsChat,
7953
+ signInWithX,
7787
7954
  sendEmailCode,
7788
7955
  confirmEmailCode,
7789
7956
  sendPhoneCode,