@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 +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +251 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/HypurrConnectProvider.tsx +36 -33
- package/src/UserProfileModal.tsx +205 -4
- package/src/agent.ts +35 -4
- package/src/types.ts +2 -2
package/package.json
CHANGED
|
@@ -55,14 +55,16 @@ import type {
|
|
|
55
55
|
/** @internal context value — extends the public type with fields used only by library internals */
|
|
56
56
|
interface InternalConnectState extends HypurrConnectState {
|
|
57
57
|
loginTelegram: () => void;
|
|
58
|
+
/** Connected EOA owner address (EOA auth only); null otherwise. */
|
|
59
|
+
eoaAddress: `0x${string}` | null;
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
const TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
|
|
61
63
|
const LEGACY_TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
|
|
62
64
|
const TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
|
|
63
|
-
const TELEGRAM_AUTH_CODE_VERIFIER_PREFIX =
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
const TELEGRAM_AUTH_CODE_VERIFIER_PREFIX = "hypurr-connect-auth-code-verifier:";
|
|
66
|
+
const TELEGRAM_AUTH_REDIRECT_URI_PREFIX =
|
|
67
|
+
"hypurr-connect-auth-redirect-uri:";
|
|
66
68
|
const TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
|
|
67
69
|
const DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
|
|
68
70
|
const DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
|
|
@@ -239,7 +241,7 @@ function withExpectedFrom(
|
|
|
239
241
|
return transaction.from ? transaction : { ...transaction, from: address };
|
|
240
242
|
}
|
|
241
243
|
|
|
242
|
-
function
|
|
244
|
+
function currentRedirectUri(): string {
|
|
243
245
|
const url = new URL(window.location.href);
|
|
244
246
|
for (const param of [
|
|
245
247
|
"code",
|
|
@@ -325,54 +327,52 @@ function codeVerifierStorageKey(state: string): string {
|
|
|
325
327
|
return `${TELEGRAM_AUTH_CODE_VERIFIER_PREFIX}${state}`;
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
function
|
|
329
|
-
return `${
|
|
330
|
+
function redirectUriStorageKey(state: string): string {
|
|
331
|
+
return `${TELEGRAM_AUTH_REDIRECT_URI_PREFIX}${state}`;
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
function storeTelegramAuthSession(
|
|
333
335
|
state: string,
|
|
334
336
|
codeVerifier: string,
|
|
335
|
-
|
|
337
|
+
redirectUri: string,
|
|
336
338
|
): void {
|
|
337
339
|
const previousState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
|
|
338
340
|
if (previousState) {
|
|
339
341
|
sessionStorage.removeItem(codeVerifierStorageKey(previousState));
|
|
340
|
-
sessionStorage.removeItem(
|
|
342
|
+
sessionStorage.removeItem(redirectUriStorageKey(previousState));
|
|
341
343
|
}
|
|
342
344
|
sessionStorage.setItem(TELEGRAM_AUTH_STATE_KEY, state);
|
|
343
345
|
sessionStorage.setItem(codeVerifierStorageKey(state), codeVerifier);
|
|
344
|
-
sessionStorage.setItem(
|
|
346
|
+
sessionStorage.setItem(redirectUriStorageKey(state), redirectUri);
|
|
345
347
|
}
|
|
346
348
|
|
|
347
349
|
function clearTelegramAuthSession(state: string): void {
|
|
348
350
|
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
349
351
|
sessionStorage.removeItem(codeVerifierStorageKey(state));
|
|
350
|
-
sessionStorage.removeItem(
|
|
352
|
+
sessionStorage.removeItem(redirectUriStorageKey(state));
|
|
351
353
|
}
|
|
352
354
|
|
|
353
|
-
function takeTelegramAuthSession(state: string):
|
|
354
|
-
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
| null {
|
|
355
|
+
function takeTelegramAuthSession(state: string): {
|
|
356
|
+
codeVerifier: string | null;
|
|
357
|
+
redirectUri: string | null;
|
|
358
|
+
} | null {
|
|
359
359
|
const expectedState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
|
|
360
360
|
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
361
361
|
if (!expectedState || state !== expectedState) {
|
|
362
362
|
if (expectedState) {
|
|
363
363
|
sessionStorage.removeItem(codeVerifierStorageKey(expectedState));
|
|
364
|
-
sessionStorage.removeItem(
|
|
364
|
+
sessionStorage.removeItem(redirectUriStorageKey(expectedState));
|
|
365
365
|
}
|
|
366
366
|
return null;
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
const codeVerifierKey = codeVerifierStorageKey(state);
|
|
370
|
-
const
|
|
370
|
+
const redirectUriKey = redirectUriStorageKey(state);
|
|
371
371
|
const codeVerifier = sessionStorage.getItem(codeVerifierKey);
|
|
372
|
-
const
|
|
372
|
+
const redirectUri = sessionStorage.getItem(redirectUriKey);
|
|
373
373
|
sessionStorage.removeItem(codeVerifierKey);
|
|
374
|
-
sessionStorage.removeItem(
|
|
375
|
-
return { codeVerifier,
|
|
374
|
+
sessionStorage.removeItem(redirectUriKey);
|
|
375
|
+
return { codeVerifier, redirectUri };
|
|
376
376
|
}
|
|
377
377
|
|
|
378
378
|
function fallbackAuthTokenUrl(authHubUrl?: string): string {
|
|
@@ -455,20 +455,20 @@ async function exchangeTelegramAuthCode({
|
|
|
455
455
|
clientId,
|
|
456
456
|
code,
|
|
457
457
|
codeVerifier,
|
|
458
|
-
|
|
458
|
+
redirectUri,
|
|
459
459
|
}: {
|
|
460
460
|
authHubUrl?: string;
|
|
461
461
|
clientId: string;
|
|
462
462
|
code: string;
|
|
463
463
|
codeVerifier: string;
|
|
464
|
-
|
|
464
|
+
redirectUri: string;
|
|
465
465
|
}): Promise<string> {
|
|
466
466
|
const body = new URLSearchParams({
|
|
467
467
|
client_id: clientId,
|
|
468
468
|
code,
|
|
469
469
|
code_verifier: codeVerifier,
|
|
470
470
|
grant_type: "authorization_code",
|
|
471
|
-
|
|
471
|
+
redirect_uri: redirectUri,
|
|
472
472
|
});
|
|
473
473
|
|
|
474
474
|
const response = await fetch(await resolveAuthTokenUrl(authHubUrl), {
|
|
@@ -640,7 +640,7 @@ export function HypurrConnectProvider({
|
|
|
640
640
|
clientId: normalizeClientId(config.clientId),
|
|
641
641
|
code: callback.code,
|
|
642
642
|
codeVerifier: authSession.codeVerifier,
|
|
643
|
-
|
|
643
|
+
redirectUri: authSession.redirectUri || currentRedirectUri(),
|
|
644
644
|
})
|
|
645
645
|
.then(acceptTelegramToken)
|
|
646
646
|
.catch((err) =>
|
|
@@ -1643,12 +1643,12 @@ export function HypurrConnectProvider({
|
|
|
1643
1643
|
const state = randomState();
|
|
1644
1644
|
const codeVerifier = randomCodeVerifier();
|
|
1645
1645
|
|
|
1646
|
-
const
|
|
1647
|
-
const
|
|
1648
|
-
typeof
|
|
1649
|
-
?
|
|
1650
|
-
:
|
|
1651
|
-
storeTelegramAuthSession(state, codeVerifier,
|
|
1646
|
+
const configuredRedirectUri = config.telegram?.redirectUri;
|
|
1647
|
+
const redirectUri =
|
|
1648
|
+
typeof configuredRedirectUri === "function"
|
|
1649
|
+
? configuredRedirectUri()
|
|
1650
|
+
: configuredRedirectUri || currentRedirectUri();
|
|
1651
|
+
storeTelegramAuthSession(state, codeVerifier, redirectUri);
|
|
1652
1652
|
|
|
1653
1653
|
const width = 520;
|
|
1654
1654
|
const height = 720;
|
|
@@ -1676,7 +1676,8 @@ export function HypurrConnectProvider({
|
|
|
1676
1676
|
"client_id",
|
|
1677
1677
|
normalizeClientId(config.clientId),
|
|
1678
1678
|
);
|
|
1679
|
-
authUrl.searchParams.set("
|
|
1679
|
+
authUrl.searchParams.set("redirect_uri", redirectUri);
|
|
1680
|
+
authUrl.searchParams.set("return_to", redirectUri);
|
|
1680
1681
|
authUrl.searchParams.set("state", state);
|
|
1681
1682
|
authUrl.searchParams.set(
|
|
1682
1683
|
"scope",
|
|
@@ -1704,7 +1705,7 @@ export function HypurrConnectProvider({
|
|
|
1704
1705
|
}, [
|
|
1705
1706
|
config.clientId,
|
|
1706
1707
|
config.telegram?.authHubUrl,
|
|
1707
|
-
config.telegram?.
|
|
1708
|
+
config.telegram?.redirectUri,
|
|
1708
1709
|
config.telegram?.scope,
|
|
1709
1710
|
]);
|
|
1710
1711
|
|
|
@@ -1898,6 +1899,7 @@ export function HypurrConnectProvider({
|
|
|
1898
1899
|
agent,
|
|
1899
1900
|
agentReady,
|
|
1900
1901
|
clearAgent: handleClearAgent,
|
|
1902
|
+
eoaAddress,
|
|
1901
1903
|
|
|
1902
1904
|
authDataMap,
|
|
1903
1905
|
authToken: tgAuthToken,
|
|
@@ -1942,6 +1944,7 @@ export function HypurrConnectProvider({
|
|
|
1942
1944
|
agent,
|
|
1943
1945
|
agentReady,
|
|
1944
1946
|
handleClearAgent,
|
|
1947
|
+
eoaAddress,
|
|
1945
1948
|
authDataMap,
|
|
1946
1949
|
tgAuthToken,
|
|
1947
1950
|
telegramRpcOptions,
|
package/src/UserProfileModal.tsx
CHANGED
|
@@ -2,6 +2,8 @@ import { AnimatePresence, motion } from "framer-motion";
|
|
|
2
2
|
import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
|
|
3
3
|
import {
|
|
4
4
|
useCallback,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
5
7
|
useState,
|
|
6
8
|
type CSSProperties,
|
|
7
9
|
type ReactNode,
|
|
@@ -13,7 +15,12 @@ import {
|
|
|
13
15
|
import { DeleteWalletModal } from "./DeleteWalletModal";
|
|
14
16
|
import { useHypurrConnectInternal } from "./HypurrConnectProvider";
|
|
15
17
|
import { RenameWalletModal } from "./RenameWalletModal";
|
|
16
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
clearAgent as clearStoredAgent,
|
|
20
|
+
loadAllAgents,
|
|
21
|
+
type LocalApprovedAgent,
|
|
22
|
+
} from "./agent";
|
|
23
|
+
import { formatAgentExpiry, getAgentExpiryTitle } from "./agentWallet";
|
|
17
24
|
import {
|
|
18
25
|
Bot,
|
|
19
26
|
Copy,
|
|
@@ -314,6 +321,9 @@ export function UserProfileModal({
|
|
|
314
321
|
deleteWallet,
|
|
315
322
|
renameWallet,
|
|
316
323
|
authMethod,
|
|
324
|
+
agent,
|
|
325
|
+
eoaAddress,
|
|
326
|
+
clearAgent: clearContextAgent,
|
|
317
327
|
} = useHypurrConnectInternal();
|
|
318
328
|
|
|
319
329
|
const [settingsTab, setSettingsTab] = useState<SettingsTab>("ui");
|
|
@@ -321,6 +331,41 @@ export function UserProfileModal({
|
|
|
321
331
|
useState<HyperliquidWallet | null>(null);
|
|
322
332
|
const [walletToRename, setWalletToRename] =
|
|
323
333
|
useState<HyperliquidWallet | null>(null);
|
|
334
|
+
const [storedAgents, setStoredAgents] = useState<LocalApprovedAgent[]>([]);
|
|
335
|
+
|
|
336
|
+
// Local approved agents live in localStorage; refresh whenever the modal opens.
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
if (isOpen) setStoredAgents(loadAllAgents());
|
|
339
|
+
}, [isOpen]);
|
|
340
|
+
|
|
341
|
+
// Merge the localStorage scan with the reactive context agent (the currently
|
|
342
|
+
// connected EOA's approved agent), deduped by owner address.
|
|
343
|
+
const localAgents = useMemo(() => {
|
|
344
|
+
const byOwner = new Map<string, LocalApprovedAgent>();
|
|
345
|
+
for (const a of storedAgents) byOwner.set(a.masterAddress.toLowerCase(), a);
|
|
346
|
+
if (agent && eoaAddress && !byOwner.has(eoaAddress.toLowerCase())) {
|
|
347
|
+
byOwner.set(eoaAddress.toLowerCase(), {
|
|
348
|
+
...agent,
|
|
349
|
+
masterAddress: eoaAddress,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return [...byOwner.values()].sort((a, b) => b.approvedAt - a.approvedAt);
|
|
353
|
+
}, [storedAgents, agent, eoaAddress]);
|
|
354
|
+
|
|
355
|
+
const handleRemoveLocalAgent = useCallback(
|
|
356
|
+
(masterAddress: string) => {
|
|
357
|
+
if (
|
|
358
|
+
eoaAddress &&
|
|
359
|
+
masterAddress.toLowerCase() === eoaAddress.toLowerCase()
|
|
360
|
+
) {
|
|
361
|
+
clearContextAgent();
|
|
362
|
+
} else {
|
|
363
|
+
clearStoredAgent(masterAddress);
|
|
364
|
+
}
|
|
365
|
+
setStoredAgents(loadAllAgents());
|
|
366
|
+
},
|
|
367
|
+
[eoaAddress, clearContextAgent],
|
|
368
|
+
);
|
|
324
369
|
|
|
325
370
|
const profilePic = user?.photoUrl || "";
|
|
326
371
|
const displayName = user?.displayName || "";
|
|
@@ -347,10 +392,12 @@ export function UserProfileModal({
|
|
|
347
392
|
);
|
|
348
393
|
|
|
349
394
|
const handleCopyAddress = useCallback(() => {
|
|
350
|
-
|
|
351
|
-
|
|
395
|
+
// Copy the full address, not the truncated display name.
|
|
396
|
+
const address = user?.address || eoaAddress || displayName;
|
|
397
|
+
if (!address) return;
|
|
398
|
+
navigator.clipboard.writeText(address);
|
|
352
399
|
onNotify?.({ type: "success", message: "Address copied" });
|
|
353
|
-
}, [displayName, onNotify]);
|
|
400
|
+
}, [user?.address, eoaAddress, displayName, onNotify]);
|
|
354
401
|
|
|
355
402
|
return (
|
|
356
403
|
<div className="hypurr-connect" style={{ display: "contents" }}>
|
|
@@ -798,6 +845,48 @@ export function UserProfileModal({
|
|
|
798
845
|
{wallets.length} wallet
|
|
799
846
|
{wallets.length !== 1 ? "s" : ""} linked
|
|
800
847
|
</p>
|
|
848
|
+
|
|
849
|
+
{localAgents.length > 0 && (
|
|
850
|
+
<div style={{ marginTop: 8 }}>
|
|
851
|
+
<p
|
|
852
|
+
style={{
|
|
853
|
+
margin: "0 0 8px",
|
|
854
|
+
color: profileColors.muted,
|
|
855
|
+
...upperLabelStyle,
|
|
856
|
+
}}
|
|
857
|
+
>
|
|
858
|
+
Agent wallets
|
|
859
|
+
</p>
|
|
860
|
+
<div
|
|
861
|
+
style={{
|
|
862
|
+
display: "flex",
|
|
863
|
+
flexDirection: "column",
|
|
864
|
+
gap: 6,
|
|
865
|
+
}}
|
|
866
|
+
>
|
|
867
|
+
{localAgents.map((agent) => (
|
|
868
|
+
<LocalAgentRow
|
|
869
|
+
key={agent.masterAddress}
|
|
870
|
+
agent={agent}
|
|
871
|
+
onRemove={() =>
|
|
872
|
+
handleRemoveLocalAgent(agent.masterAddress)
|
|
873
|
+
}
|
|
874
|
+
/>
|
|
875
|
+
))}
|
|
876
|
+
</div>
|
|
877
|
+
<p
|
|
878
|
+
style={{
|
|
879
|
+
margin: "8px 0 0",
|
|
880
|
+
fontSize: 12.5,
|
|
881
|
+
lineHeight: "1rem",
|
|
882
|
+
color: profileColors.subdued,
|
|
883
|
+
textAlign: "center",
|
|
884
|
+
}}
|
|
885
|
+
>
|
|
886
|
+
Approved on this device
|
|
887
|
+
</p>
|
|
888
|
+
</div>
|
|
889
|
+
)}
|
|
801
890
|
</div>
|
|
802
891
|
)}
|
|
803
892
|
</div>
|
|
@@ -1039,6 +1128,118 @@ function WalletRow({
|
|
|
1039
1128
|
);
|
|
1040
1129
|
}
|
|
1041
1130
|
|
|
1131
|
+
function shortAddress(address: string): string {
|
|
1132
|
+
return `${address.slice(0, 6)}...${address.slice(-4)}`;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Older builds stored validUntil scaled ×1000 (µs). Coerce those back to ms.
|
|
1136
|
+
function normalizeExpiryMs(validUntil: number): number {
|
|
1137
|
+
return validUntil > 1e14 ? Math.round(validUntil / 1000) : validUntil;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function LocalAgentRow({
|
|
1141
|
+
agent,
|
|
1142
|
+
onRemove,
|
|
1143
|
+
}: {
|
|
1144
|
+
agent: LocalApprovedAgent;
|
|
1145
|
+
onRemove: () => void;
|
|
1146
|
+
}) {
|
|
1147
|
+
const [hovered, setHovered] = useState(false);
|
|
1148
|
+
const expiresAtMs = normalizeExpiryMs(agent.validUntil);
|
|
1149
|
+
const isExpired = expiresAtMs <= Date.now();
|
|
1150
|
+
const agentTypeColor = "rgb(var(--blue-500))";
|
|
1151
|
+
return (
|
|
1152
|
+
<div
|
|
1153
|
+
style={{
|
|
1154
|
+
...walletRowStyle,
|
|
1155
|
+
background: hovered
|
|
1156
|
+
? profileColors.surfaceBtnHover
|
|
1157
|
+
: profileColors.surfaceBtn,
|
|
1158
|
+
borderColor: hovered
|
|
1159
|
+
? profileColors.surfaceBdHover
|
|
1160
|
+
: profileColors.surfaceBd,
|
|
1161
|
+
}}
|
|
1162
|
+
onMouseEnter={() => setHovered(true)}
|
|
1163
|
+
onMouseLeave={() => setHovered(false)}
|
|
1164
|
+
>
|
|
1165
|
+
<div
|
|
1166
|
+
style={{
|
|
1167
|
+
width: 32,
|
|
1168
|
+
height: 32,
|
|
1169
|
+
borderRadius: 6,
|
|
1170
|
+
background: "rgb(var(--blue-500) / 0.16)",
|
|
1171
|
+
border: "1px solid rgb(var(--blue-500) / 0.34)",
|
|
1172
|
+
display: "flex",
|
|
1173
|
+
alignItems: "center",
|
|
1174
|
+
justifyContent: "center",
|
|
1175
|
+
flexShrink: 0,
|
|
1176
|
+
color: agentTypeColor,
|
|
1177
|
+
}}
|
|
1178
|
+
>
|
|
1179
|
+
<Bot size={15} />
|
|
1180
|
+
</div>
|
|
1181
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
1182
|
+
<p
|
|
1183
|
+
style={{
|
|
1184
|
+
margin: 0,
|
|
1185
|
+
fontSize: 12.5,
|
|
1186
|
+
lineHeight: "1rem",
|
|
1187
|
+
fontWeight: 500,
|
|
1188
|
+
color: profileColors.text,
|
|
1189
|
+
fontFamily: fontFamily.mono,
|
|
1190
|
+
overflow: "hidden",
|
|
1191
|
+
textOverflow: "ellipsis",
|
|
1192
|
+
whiteSpace: "nowrap",
|
|
1193
|
+
}}
|
|
1194
|
+
>
|
|
1195
|
+
{shortAddress(agent.address)}
|
|
1196
|
+
</p>
|
|
1197
|
+
<p
|
|
1198
|
+
style={{
|
|
1199
|
+
margin: "2px 0 0",
|
|
1200
|
+
fontSize: 12.5,
|
|
1201
|
+
lineHeight: "1rem",
|
|
1202
|
+
color: isExpired ? EXPIRED_AGENT_COLOR : profileColors.muted,
|
|
1203
|
+
}}
|
|
1204
|
+
>
|
|
1205
|
+
{isExpired ? "Expired" : "Valid until"}{" "}
|
|
1206
|
+
{formatAgentExpiry(expiresAtMs)}
|
|
1207
|
+
</p>
|
|
1208
|
+
<p
|
|
1209
|
+
style={{
|
|
1210
|
+
margin: "2px 0 0",
|
|
1211
|
+
fontSize: 12.5,
|
|
1212
|
+
lineHeight: "1rem",
|
|
1213
|
+
color: profileColors.subdued,
|
|
1214
|
+
fontFamily: fontFamily.mono,
|
|
1215
|
+
}}
|
|
1216
|
+
>
|
|
1217
|
+
Owner {shortAddress(agent.masterAddress)}
|
|
1218
|
+
</p>
|
|
1219
|
+
</div>
|
|
1220
|
+
<div
|
|
1221
|
+
style={{
|
|
1222
|
+
display: "flex",
|
|
1223
|
+
alignItems: "center",
|
|
1224
|
+
flexShrink: 0,
|
|
1225
|
+
opacity: hovered ? 1 : 0,
|
|
1226
|
+
transition: "opacity 120ms",
|
|
1227
|
+
}}
|
|
1228
|
+
>
|
|
1229
|
+
<IconBtn
|
|
1230
|
+
color="#ef4444"
|
|
1231
|
+
hoverBackgroundColor="rgba(239,68,68,0.1)"
|
|
1232
|
+
title="Remove local agent"
|
|
1233
|
+
ariaLabel={`Remove local agent for ${shortAddress(agent.masterAddress)}`}
|
|
1234
|
+
onClick={onRemove}
|
|
1235
|
+
>
|
|
1236
|
+
<Trash2 size={13} />
|
|
1237
|
+
</IconBtn>
|
|
1238
|
+
</div>
|
|
1239
|
+
</div>
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1042
1243
|
function IconBtn({
|
|
1043
1244
|
children,
|
|
1044
1245
|
color,
|
package/src/agent.ts
CHANGED
|
@@ -26,6 +26,39 @@ export function clearAgent(masterAddress: string): void {
|
|
|
26
26
|
localStorage.removeItem(storageKey(masterAddress));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/** A locally-stored agent alongside the master address it was approved for. */
|
|
30
|
+
export interface LocalApprovedAgent extends StoredAgent {
|
|
31
|
+
masterAddress: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Enumerate every agent approved and stored locally on this device by scanning
|
|
36
|
+
* localStorage for the agent storage prefix. Newest approvals first.
|
|
37
|
+
*/
|
|
38
|
+
export function loadAllAgents(): LocalApprovedAgent[] {
|
|
39
|
+
const agents: LocalApprovedAgent[] = [];
|
|
40
|
+
try {
|
|
41
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
42
|
+
const key = localStorage.key(i);
|
|
43
|
+
if (!key || !key.startsWith(`${AGENT_STORAGE_PREFIX}:`)) continue;
|
|
44
|
+
const raw = localStorage.getItem(key);
|
|
45
|
+
if (!raw) continue;
|
|
46
|
+
try {
|
|
47
|
+
const agent = JSON.parse(raw) as StoredAgent;
|
|
48
|
+
agents.push({
|
|
49
|
+
...agent,
|
|
50
|
+
masterAddress: key.slice(key.indexOf(":") + 1),
|
|
51
|
+
});
|
|
52
|
+
} catch {
|
|
53
|
+
// Skip malformed entries.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
return agents.sort((a, b) => b.approvedAt - a.approvedAt);
|
|
60
|
+
}
|
|
61
|
+
|
|
29
62
|
/**
|
|
30
63
|
* Generate a random 32-byte private key and derive its address using the
|
|
31
64
|
* local PrivateKeySigner compatibility wrapper.
|
|
@@ -73,10 +106,8 @@ async function fetchExtraAgents(
|
|
|
73
106
|
const agents: unknown = await res.json();
|
|
74
107
|
if (!Array.isArray(agents)) return [];
|
|
75
108
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
validUntil: agent.validUntil * 1000,
|
|
79
|
-
}));
|
|
109
|
+
// The extraAgents API already returns validUntil in epoch ms.
|
|
110
|
+
return agents as ExtraAgent[];
|
|
80
111
|
}
|
|
81
112
|
|
|
82
113
|
/**
|
package/src/types.ts
CHANGED
|
@@ -33,8 +33,8 @@ export interface HypurrConnectConfig {
|
|
|
33
33
|
telegram?: {
|
|
34
34
|
/** Auth hub login URL. Defaults to https://auth.hypurr.fun/login. */
|
|
35
35
|
authHubUrl?: string;
|
|
36
|
-
/** Optional
|
|
37
|
-
|
|
36
|
+
/** Optional OAuth redirect URI. Defaults to the current page without auth query params. */
|
|
37
|
+
redirectUri?: string | (() => string);
|
|
38
38
|
/** Requested hub scopes. Defaults to the scopes required by this SDK. */
|
|
39
39
|
scope?: string | string[];
|
|
40
40
|
/** @deprecated Telegram login is handled by the auth hub; this option is ignored. */
|