@hfunlabs/hypurr-connect 0.1.24 → 0.1.26

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/README.md CHANGED
@@ -126,7 +126,7 @@ interface HypurrConnectConfig {
126
126
  isTestnet?: boolean; // Use testnet endpoints (default: false)
127
127
  telegram: {
128
128
  authHubUrl?: string; // Auth hub URL (default: https://auth.hypurr.fun/login)
129
- returnTo?: string | (() => string); // Callback URL (default: current page)
129
+ redirectUri?: string | (() => string); // OAuth redirect URI (default: current page)
130
130
  scope?: string | string[]; // Requested JWT scopes
131
131
  };
132
132
  }
@@ -148,15 +148,15 @@ for generated protobuf service clients.
148
148
 
149
149
  1. User clicks "Telegram" in the `LoginModal`.
150
150
  2. The SDK opens the configured auth hub in a popup with `client_id`,
151
- `return_to`, `state`, requested `scope`, `code_challenge`, and
152
- `code_challenge_method=S256`.
153
- 3. The auth hub performs Telegram login and redirects back to `return_to` with
151
+ `redirect_uri`, `return_to`, `state`, requested `scope`, `code_challenge`,
152
+ and `code_challenge_method=S256`.
153
+ 3. The auth hub performs Telegram login and redirects back to `redirect_uri` with
154
154
  either a legacy scoped JWT or an authorization code.
155
155
  4. The popup callback page posts `{ token, state }` or `{ code, state }` to the
156
156
  opener with `postMessage` and closes.
157
157
  5. The opener validates `state`. Legacy tokens are stored directly; auth codes
158
158
  are exchanged at the OAuth metadata `token_endpoint` with the stored PKCE
159
- verifier.
159
+ verifier and the original callback URL as `redirect_uri`.
160
160
  6. The SDK calls the Hypurr gRPC backend with `Authorization: Bearer <jwt>`
161
161
  metadata.
162
162
  7. An `ExchangeClient` is created with `GrpcExchangeTransport`; exchange
@@ -164,8 +164,8 @@ for generated protobuf service clients.
164
164
  8. The JWT session is persisted in localStorage (`hypurr-connect-tg-jwt`).
165
165
 
166
166
  If the popup is blocked, the SDK falls back to a full-page redirect. The
167
- default `returnTo` is the current page with auth query params removed; custom
168
- `returnTo` URLs should load the app and mount `HypurrConnectProvider` so the
167
+ default `redirectUri` is the current page with auth query params removed; custom
168
+ `redirectUri` URLs should load the app and mount `HypurrConnectProvider` so the
169
169
  popup callback bridge can run.
170
170
 
171
171
  ### EOA Wallet Login
package/dist/index.d.ts CHANGED
@@ -27,8 +27,8 @@ interface HypurrConnectConfig {
27
27
  telegram?: {
28
28
  /** Auth hub login URL. Defaults to https://auth.hypurr.fun/login. */
29
29
  authHubUrl?: string;
30
- /** Optional callback URL. Defaults to the current page without auth query params. */
31
- returnTo?: string | (() => string);
30
+ /** Optional OAuth redirect URI. Defaults to the current page without auth query params. */
31
+ redirectUri?: string | (() => string);
32
32
  /** Requested hub scopes. Defaults to the scopes required by this SDK. */
33
33
  scope?: string | string[];
34
34
  /** @deprecated Telegram login is handled by the auth hub; this option is ignored. */
package/dist/index.js CHANGED
@@ -80,6 +80,28 @@ function saveAgent(masterAddress, agent) {
80
80
  function clearAgent(masterAddress) {
81
81
  localStorage.removeItem(storageKey(masterAddress));
82
82
  }
83
+ function loadAllAgents() {
84
+ const agents = [];
85
+ try {
86
+ for (let i = 0; i < localStorage.length; i++) {
87
+ const key = localStorage.key(i);
88
+ if (!key || !key.startsWith(`${AGENT_STORAGE_PREFIX}:`)) continue;
89
+ const raw = localStorage.getItem(key);
90
+ if (!raw) continue;
91
+ try {
92
+ const agent = JSON.parse(raw);
93
+ agents.push({
94
+ ...agent,
95
+ masterAddress: key.slice(key.indexOf(":") + 1)
96
+ });
97
+ } catch {
98
+ }
99
+ }
100
+ } catch {
101
+ return [];
102
+ }
103
+ return agents.sort((a, b) => b.approvedAt - a.approvedAt);
104
+ }
83
105
  async function generateAgentKey() {
84
106
  const bytes = crypto.getRandomValues(new Uint8Array(32));
85
107
  const hex = Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
@@ -100,10 +122,7 @@ async function fetchExtraAgents(userAddress, isTestnet) {
100
122
  if (!res.ok) return [];
101
123
  const agents = await res.json();
102
124
  if (!Array.isArray(agents)) return [];
103
- return agents.map((agent) => ({
104
- ...agent,
105
- validUntil: agent.validUntil * 1e3
106
- }));
125
+ return agents;
107
126
  }
108
127
  async function fetchActiveAgent(userAddress, isTestnet) {
109
128
  const nowMs = Date.now();
@@ -296,7 +315,7 @@ var TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
296
315
  var LEGACY_TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
297
316
  var TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
298
317
  var TELEGRAM_AUTH_CODE_VERIFIER_PREFIX = "hypurr-connect-auth-code-verifier:";
299
- var TELEGRAM_AUTH_RETURN_TO_PREFIX = "hypurr-connect-auth-return-to:";
318
+ var TELEGRAM_AUTH_REDIRECT_URI_PREFIX = "hypurr-connect-auth-redirect-uri:";
300
319
  var TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
301
320
  var DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
302
321
  var DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
@@ -429,7 +448,7 @@ function withExpectedFrom(transaction, address) {
429
448
  }
430
449
  return transaction.from ? transaction : { ...transaction, from: address };
431
450
  }
432
- function currentReturnTo() {
451
+ function currentRedirectUri() {
433
452
  const url = new URL(window.location.href);
434
453
  for (const param of [
435
454
  "code",
@@ -506,23 +525,23 @@ function normalizeClientId(clientId) {
506
525
  function codeVerifierStorageKey(state) {
507
526
  return `${TELEGRAM_AUTH_CODE_VERIFIER_PREFIX}${state}`;
508
527
  }
509
- function returnToStorageKey(state) {
510
- return `${TELEGRAM_AUTH_RETURN_TO_PREFIX}${state}`;
528
+ function redirectUriStorageKey(state) {
529
+ return `${TELEGRAM_AUTH_REDIRECT_URI_PREFIX}${state}`;
511
530
  }
512
- function storeTelegramAuthSession(state, codeVerifier, returnTo) {
531
+ function storeTelegramAuthSession(state, codeVerifier, redirectUri) {
513
532
  const previousState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
514
533
  if (previousState) {
515
534
  sessionStorage.removeItem(codeVerifierStorageKey(previousState));
516
- sessionStorage.removeItem(returnToStorageKey(previousState));
535
+ sessionStorage.removeItem(redirectUriStorageKey(previousState));
517
536
  }
518
537
  sessionStorage.setItem(TELEGRAM_AUTH_STATE_KEY, state);
519
538
  sessionStorage.setItem(codeVerifierStorageKey(state), codeVerifier);
520
- sessionStorage.setItem(returnToStorageKey(state), returnTo);
539
+ sessionStorage.setItem(redirectUriStorageKey(state), redirectUri);
521
540
  }
522
541
  function clearTelegramAuthSession(state) {
523
542
  sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
524
543
  sessionStorage.removeItem(codeVerifierStorageKey(state));
525
- sessionStorage.removeItem(returnToStorageKey(state));
544
+ sessionStorage.removeItem(redirectUriStorageKey(state));
526
545
  }
527
546
  function takeTelegramAuthSession(state) {
528
547
  const expectedState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
@@ -530,17 +549,17 @@ function takeTelegramAuthSession(state) {
530
549
  if (!expectedState || state !== expectedState) {
531
550
  if (expectedState) {
532
551
  sessionStorage.removeItem(codeVerifierStorageKey(expectedState));
533
- sessionStorage.removeItem(returnToStorageKey(expectedState));
552
+ sessionStorage.removeItem(redirectUriStorageKey(expectedState));
534
553
  }
535
554
  return null;
536
555
  }
537
556
  const codeVerifierKey = codeVerifierStorageKey(state);
538
- const returnToKey = returnToStorageKey(state);
557
+ const redirectUriKey = redirectUriStorageKey(state);
539
558
  const codeVerifier = sessionStorage.getItem(codeVerifierKey);
540
- const returnTo = sessionStorage.getItem(returnToKey);
559
+ const redirectUri = sessionStorage.getItem(redirectUriKey);
541
560
  sessionStorage.removeItem(codeVerifierKey);
542
- sessionStorage.removeItem(returnToKey);
543
- return { codeVerifier, returnTo };
561
+ sessionStorage.removeItem(redirectUriKey);
562
+ return { codeVerifier, redirectUri };
544
563
  }
545
564
  function fallbackAuthTokenUrl(authHubUrl) {
546
565
  const url = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
@@ -604,14 +623,14 @@ async function exchangeTelegramAuthCode({
604
623
  clientId,
605
624
  code,
606
625
  codeVerifier,
607
- returnTo
626
+ redirectUri
608
627
  }) {
609
628
  const body = new URLSearchParams({
610
629
  client_id: clientId,
611
630
  code,
612
631
  code_verifier: codeVerifier,
613
632
  grant_type: "authorization_code",
614
- return_to: returnTo
633
+ redirect_uri: redirectUri
615
634
  });
616
635
  const response = await fetch(await resolveAuthTokenUrl(authHubUrl), {
617
636
  method: "POST",
@@ -730,7 +749,7 @@ function HypurrConnectProvider({
730
749
  clientId: normalizeClientId(config.clientId),
731
750
  code: callback.code,
732
751
  codeVerifier: authSession.codeVerifier,
733
- returnTo: authSession.returnTo || currentReturnTo()
752
+ redirectUri: authSession.redirectUri || currentRedirectUri()
734
753
  }).then(acceptTelegramToken).catch(
735
754
  (err) => setTgError(err instanceof Error ? err.message : String(err))
736
755
  ).finally(() => setTgLoading(false));
@@ -1529,9 +1548,9 @@ function HypurrConnectProvider({
1529
1548
  const loginTelegram = useCallback(() => {
1530
1549
  const state = randomState();
1531
1550
  const codeVerifier = randomCodeVerifier();
1532
- const configuredReturnTo = config.telegram?.returnTo;
1533
- const returnTo = typeof configuredReturnTo === "function" ? configuredReturnTo() : configuredReturnTo || currentReturnTo();
1534
- storeTelegramAuthSession(state, codeVerifier, returnTo);
1551
+ const configuredRedirectUri = config.telegram?.redirectUri;
1552
+ const redirectUri = typeof configuredRedirectUri === "function" ? configuredRedirectUri() : configuredRedirectUri || currentRedirectUri();
1553
+ storeTelegramAuthSession(state, codeVerifier, redirectUri);
1535
1554
  const width = 520;
1536
1555
  const height = 720;
1537
1556
  const left = window.screenX + Math.max(0, (window.outerWidth - width) / 2);
@@ -1557,7 +1576,8 @@ function HypurrConnectProvider({
1557
1576
  "client_id",
1558
1577
  normalizeClientId(config.clientId)
1559
1578
  );
1560
- authUrl.searchParams.set("return_to", returnTo);
1579
+ authUrl.searchParams.set("redirect_uri", redirectUri);
1580
+ authUrl.searchParams.set("return_to", redirectUri);
1561
1581
  authUrl.searchParams.set("state", state);
1562
1582
  authUrl.searchParams.set(
1563
1583
  "scope",
@@ -1583,7 +1603,7 @@ function HypurrConnectProvider({
1583
1603
  }, [
1584
1604
  config.clientId,
1585
1605
  config.telegram?.authHubUrl,
1586
- config.telegram?.returnTo,
1606
+ config.telegram?.redirectUri,
1587
1607
  config.telegram?.scope
1588
1608
  ]);
1589
1609
  const connectEoa = useCallback(
@@ -1746,6 +1766,7 @@ function HypurrConnectProvider({
1746
1766
  agent,
1747
1767
  agentReady,
1748
1768
  clearAgent: handleClearAgent,
1769
+ eoaAddress,
1749
1770
  authDataMap,
1750
1771
  authToken: tgAuthToken,
1751
1772
  telegramRpcOptions,
@@ -1789,6 +1810,7 @@ function HypurrConnectProvider({
1789
1810
  agent,
1790
1811
  agentReady,
1791
1812
  handleClearAgent,
1813
+ eoaAddress,
1792
1814
  authDataMap,
1793
1815
  tgAuthToken,
1794
1816
  telegramRpcOptions,
@@ -3296,7 +3318,7 @@ import { AnimatePresence as AnimatePresence7, motion as motion7 } from "framer-m
3296
3318
  import {
3297
3319
  Fragment as Fragment8,
3298
3320
  useCallback as useCallback9,
3299
- useMemo as useMemo3,
3321
+ useMemo as useMemo4,
3300
3322
  useState as useState8
3301
3323
  } from "react";
3302
3324
 
@@ -3402,6 +3424,8 @@ function AgentExpiryWarningIcon({
3402
3424
  import { AnimatePresence as AnimatePresence6, motion as motion6 } from "framer-motion";
3403
3425
  import {
3404
3426
  useCallback as useCallback8,
3427
+ useEffect as useEffect3,
3428
+ useMemo as useMemo3,
3405
3429
  useState as useState7
3406
3430
  } from "react";
3407
3431
 
@@ -4360,11 +4384,40 @@ function UserProfileModal({
4360
4384
  wallets: userWallets,
4361
4385
  deleteWallet,
4362
4386
  renameWallet,
4363
- authMethod
4387
+ authMethod,
4388
+ agent,
4389
+ eoaAddress,
4390
+ clearAgent: clearContextAgent
4364
4391
  } = useHypurrConnectInternal();
4365
4392
  const [settingsTab, setSettingsTab] = useState7("ui");
4366
4393
  const [walletToDelete, setWalletToDelete] = useState7(null);
4367
4394
  const [walletToRename, setWalletToRename] = useState7(null);
4395
+ const [storedAgents, setStoredAgents] = useState7([]);
4396
+ useEffect3(() => {
4397
+ if (isOpen) setStoredAgents(loadAllAgents());
4398
+ }, [isOpen]);
4399
+ const localAgents = useMemo3(() => {
4400
+ const byOwner = /* @__PURE__ */ new Map();
4401
+ for (const a of storedAgents) byOwner.set(a.masterAddress.toLowerCase(), a);
4402
+ if (agent && eoaAddress && !byOwner.has(eoaAddress.toLowerCase())) {
4403
+ byOwner.set(eoaAddress.toLowerCase(), {
4404
+ ...agent,
4405
+ masterAddress: eoaAddress
4406
+ });
4407
+ }
4408
+ return [...byOwner.values()].sort((a, b) => b.approvedAt - a.approvedAt);
4409
+ }, [storedAgents, agent, eoaAddress]);
4410
+ const handleRemoveLocalAgent = useCallback8(
4411
+ (masterAddress) => {
4412
+ if (eoaAddress && masterAddress.toLowerCase() === eoaAddress.toLowerCase()) {
4413
+ clearContextAgent();
4414
+ } else {
4415
+ clearAgent(masterAddress);
4416
+ }
4417
+ setStoredAgents(loadAllAgents());
4418
+ },
4419
+ [eoaAddress, clearContextAgent]
4420
+ );
4368
4421
  const profilePic = user?.photoUrl || "";
4369
4422
  const displayName = user?.displayName || "";
4370
4423
  const hfunScore = user?.hfunScore ?? null;
@@ -4387,10 +4440,11 @@ function UserProfileModal({
4387
4440
  [renameWallet, onWalletRenamed]
4388
4441
  );
4389
4442
  const handleCopyAddress = useCallback8(() => {
4390
- if (!displayName) return;
4391
- navigator.clipboard.writeText(displayName);
4443
+ const address = user?.address || eoaAddress || displayName;
4444
+ if (!address) return;
4445
+ navigator.clipboard.writeText(address);
4392
4446
  onNotify?.({ type: "success", message: "Address copied" });
4393
- }, [displayName, onNotify]);
4447
+ }, [user?.address, eoaAddress, displayName, onNotify]);
4394
4448
  return /* @__PURE__ */ jsxs10("div", { className: "hypurr-connect", style: { display: "contents" }, children: [
4395
4449
  /* @__PURE__ */ jsx11(AnimatePresence6, { children: isOpen && /* @__PURE__ */ jsxs10(Fragment7, { children: [
4396
4450
  /* @__PURE__ */ jsx11(SpinKeyframes, {}),
@@ -4904,7 +4958,51 @@ function UserProfileModal({
4904
4958
  " linked"
4905
4959
  ]
4906
4960
  }
4907
- )
4961
+ ),
4962
+ localAgents.length > 0 && /* @__PURE__ */ jsxs10("div", { style: { marginTop: 8 }, children: [
4963
+ /* @__PURE__ */ jsx11(
4964
+ "p",
4965
+ {
4966
+ style: {
4967
+ margin: "0 0 8px",
4968
+ color: profileColors.muted,
4969
+ ...upperLabelStyle
4970
+ },
4971
+ children: "Agent wallets"
4972
+ }
4973
+ ),
4974
+ /* @__PURE__ */ jsx11(
4975
+ "div",
4976
+ {
4977
+ style: {
4978
+ display: "flex",
4979
+ flexDirection: "column",
4980
+ gap: 6
4981
+ },
4982
+ children: localAgents.map((agent2) => /* @__PURE__ */ jsx11(
4983
+ LocalAgentRow,
4984
+ {
4985
+ agent: agent2,
4986
+ onRemove: () => handleRemoveLocalAgent(agent2.masterAddress)
4987
+ },
4988
+ agent2.masterAddress
4989
+ ))
4990
+ }
4991
+ ),
4992
+ /* @__PURE__ */ jsx11(
4993
+ "p",
4994
+ {
4995
+ style: {
4996
+ margin: "8px 0 0",
4997
+ fontSize: 12.5,
4998
+ lineHeight: "1rem",
4999
+ color: profileColors.subdued,
5000
+ textAlign: "center"
5001
+ },
5002
+ children: "Approved on this device"
5003
+ }
5004
+ )
5005
+ ] })
4908
5006
  ]
4909
5007
  }
4910
5008
  )
@@ -5148,6 +5246,127 @@ function WalletRow({
5148
5246
  }
5149
5247
  );
5150
5248
  }
5249
+ function shortAddress(address) {
5250
+ return `${address.slice(0, 6)}...${address.slice(-4)}`;
5251
+ }
5252
+ function normalizeExpiryMs(validUntil) {
5253
+ return validUntil > 1e14 ? Math.round(validUntil / 1e3) : validUntil;
5254
+ }
5255
+ function LocalAgentRow({
5256
+ agent,
5257
+ onRemove
5258
+ }) {
5259
+ const [hovered, setHovered] = useState7(false);
5260
+ const expiresAtMs = normalizeExpiryMs(agent.validUntil);
5261
+ const isExpired = expiresAtMs <= Date.now();
5262
+ const agentTypeColor = "rgb(var(--blue-500))";
5263
+ return /* @__PURE__ */ jsxs10(
5264
+ "div",
5265
+ {
5266
+ style: {
5267
+ ...walletRowStyle,
5268
+ background: hovered ? profileColors.surfaceBtnHover : profileColors.surfaceBtn,
5269
+ borderColor: hovered ? profileColors.surfaceBdHover : profileColors.surfaceBd
5270
+ },
5271
+ onMouseEnter: () => setHovered(true),
5272
+ onMouseLeave: () => setHovered(false),
5273
+ children: [
5274
+ /* @__PURE__ */ jsx11(
5275
+ "div",
5276
+ {
5277
+ style: {
5278
+ width: 32,
5279
+ height: 32,
5280
+ borderRadius: 6,
5281
+ background: "rgb(var(--blue-500) / 0.16)",
5282
+ border: "1px solid rgb(var(--blue-500) / 0.34)",
5283
+ display: "flex",
5284
+ alignItems: "center",
5285
+ justifyContent: "center",
5286
+ flexShrink: 0,
5287
+ color: agentTypeColor
5288
+ },
5289
+ children: /* @__PURE__ */ jsx11(Bot, { size: 15 })
5290
+ }
5291
+ ),
5292
+ /* @__PURE__ */ jsxs10("div", { style: { flex: 1, minWidth: 0 }, children: [
5293
+ /* @__PURE__ */ jsx11(
5294
+ "p",
5295
+ {
5296
+ style: {
5297
+ margin: 0,
5298
+ fontSize: 12.5,
5299
+ lineHeight: "1rem",
5300
+ fontWeight: 500,
5301
+ color: profileColors.text,
5302
+ fontFamily: fontFamily.mono,
5303
+ overflow: "hidden",
5304
+ textOverflow: "ellipsis",
5305
+ whiteSpace: "nowrap"
5306
+ },
5307
+ children: shortAddress(agent.address)
5308
+ }
5309
+ ),
5310
+ /* @__PURE__ */ jsxs10(
5311
+ "p",
5312
+ {
5313
+ style: {
5314
+ margin: "2px 0 0",
5315
+ fontSize: 12.5,
5316
+ lineHeight: "1rem",
5317
+ color: isExpired ? EXPIRED_AGENT_COLOR : profileColors.muted
5318
+ },
5319
+ children: [
5320
+ isExpired ? "Expired" : "Valid until",
5321
+ " ",
5322
+ formatAgentExpiry(expiresAtMs)
5323
+ ]
5324
+ }
5325
+ ),
5326
+ /* @__PURE__ */ jsxs10(
5327
+ "p",
5328
+ {
5329
+ style: {
5330
+ margin: "2px 0 0",
5331
+ fontSize: 12.5,
5332
+ lineHeight: "1rem",
5333
+ color: profileColors.subdued,
5334
+ fontFamily: fontFamily.mono
5335
+ },
5336
+ children: [
5337
+ "Owner ",
5338
+ shortAddress(agent.masterAddress)
5339
+ ]
5340
+ }
5341
+ )
5342
+ ] }),
5343
+ /* @__PURE__ */ jsx11(
5344
+ "div",
5345
+ {
5346
+ style: {
5347
+ display: "flex",
5348
+ alignItems: "center",
5349
+ flexShrink: 0,
5350
+ opacity: hovered ? 1 : 0,
5351
+ transition: "opacity 120ms"
5352
+ },
5353
+ children: /* @__PURE__ */ jsx11(
5354
+ IconBtn,
5355
+ {
5356
+ color: "#ef4444",
5357
+ hoverBackgroundColor: "rgba(239,68,68,0.1)",
5358
+ title: "Remove local agent",
5359
+ ariaLabel: `Remove local agent for ${shortAddress(agent.masterAddress)}`,
5360
+ onClick: onRemove,
5361
+ children: /* @__PURE__ */ jsx11(Trash2, { size: 13 })
5362
+ }
5363
+ )
5364
+ }
5365
+ )
5366
+ ]
5367
+ }
5368
+ );
5369
+ }
5151
5370
  function IconBtn({
5152
5371
  children,
5153
5372
  color,
@@ -5322,7 +5541,7 @@ function WalletSelectorDropdown({
5322
5541
  setProfileOpen(true);
5323
5542
  onClose();
5324
5543
  }, [onClose]);
5325
- const walletListEntries = useMemo3(
5544
+ const walletListEntries = useMemo4(
5326
5545
  () => buildWalletListEntries(Array.isArray(wallets) ? wallets : void 0),
5327
5546
  [wallets]
5328
5547
  );