@hfunlabs/hypurr-connect 0.1.22 → 0.1.24
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 +16 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +285 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/HypurrConnectProvider.tsx +366 -65
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ pnpm add @hfunlabs/hyperliquid @protobuf-ts/grpcweb-transport @protobuf-ts/runti
|
|
|
44
44
|
import { HypurrConnectProvider } from "@hfunlabs/hypurr-connect";
|
|
45
45
|
|
|
46
46
|
const config = {
|
|
47
|
-
|
|
47
|
+
clientId: "your-client-id",
|
|
48
48
|
isTestnet: false,
|
|
49
49
|
grpcUrl: "https://grpc.hypurr.fun",
|
|
50
50
|
telegram: {
|
|
@@ -119,7 +119,7 @@ function AppShell() {
|
|
|
119
119
|
|
|
120
120
|
```typescript
|
|
121
121
|
interface HypurrConnectConfig {
|
|
122
|
-
|
|
122
|
+
clientId: string; // Auth hub client identifier
|
|
123
123
|
grpcUrl?: string; // gRPC-web base URL (default: https://grpc.hypurr.fun)
|
|
124
124
|
mediaUrl?: string; // Media base URL (default: https://media.hypurr.fun)
|
|
125
125
|
grpcTimeout?: number; // Request timeout in ms (default: 15000)
|
|
@@ -134,7 +134,8 @@ interface HypurrConnectConfig {
|
|
|
134
134
|
|
|
135
135
|
The SDK no longer renders Telegram's login widget or opens `oauth.telegram.org`
|
|
136
136
|
directly. Telegram login is delegated to the Hypurr auth hub in a popup, and the
|
|
137
|
-
popup posts
|
|
137
|
+
popup posts either a legacy scoped JWT or an authorization code back to the
|
|
138
|
+
original page.
|
|
138
139
|
|
|
139
140
|
### Dependencies
|
|
140
141
|
|
|
@@ -147,16 +148,20 @@ for generated protobuf service clients.
|
|
|
147
148
|
|
|
148
149
|
1. User clicks "Telegram" in the `LoginModal`.
|
|
149
150
|
2. The SDK opens the configured auth hub in a popup with `client_id`,
|
|
150
|
-
`return_to`, `state`,
|
|
151
|
+
`return_to`, `state`, requested `scope`, `code_challenge`, and
|
|
152
|
+
`code_challenge_method=S256`.
|
|
151
153
|
3. The auth hub performs Telegram login and redirects back to `return_to` with
|
|
152
|
-
a scoped JWT.
|
|
153
|
-
4. The popup callback page posts `{ token, state }` to the
|
|
154
|
-
`postMessage` and closes.
|
|
155
|
-
5. The opener validates `state
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
either a legacy scoped JWT or an authorization code.
|
|
155
|
+
4. The popup callback page posts `{ token, state }` or `{ code, state }` to the
|
|
156
|
+
opener with `postMessage` and closes.
|
|
157
|
+
5. The opener validates `state`. Legacy tokens are stored directly; auth codes
|
|
158
|
+
are exchanged at the OAuth metadata `token_endpoint` with the stored PKCE
|
|
159
|
+
verifier.
|
|
160
|
+
6. The SDK calls the Hypurr gRPC backend with `Authorization: Bearer <jwt>`
|
|
161
|
+
metadata.
|
|
162
|
+
7. An `ExchangeClient` is created with `GrpcExchangeTransport`; exchange
|
|
158
163
|
actions are still signed server-side by the Hypurr backend.
|
|
159
|
-
|
|
164
|
+
8. The JWT session is persisted in localStorage (`hypurr-connect-tg-jwt`).
|
|
160
165
|
|
|
161
166
|
If the popup is blocked, the SDK falls back to a full-page redirect. The
|
|
162
167
|
default `returnTo` is the current page with auth query params removed; custom
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { AbstractViemLocalAccount } from '@hfunlabs/hyperliquid/signing';
|
|
|
15
15
|
|
|
16
16
|
interface HypurrConnectConfig {
|
|
17
17
|
/** Auth hub client identifier. Sent as `client_id` during Telegram login. */
|
|
18
|
-
|
|
18
|
+
clientId: string;
|
|
19
19
|
/** gRPC-web base URL. Defaults to https://grpc.hypurr.fun. */
|
|
20
20
|
grpcUrl?: string;
|
|
21
21
|
/** Media base URL for user-uploaded assets. Defaults to https://media.hypurr.fun. */
|
package/dist/index.js
CHANGED
|
@@ -295,6 +295,8 @@ import { jsx } from "react/jsx-runtime";
|
|
|
295
295
|
var TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
|
|
296
296
|
var LEGACY_TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
|
|
297
297
|
var TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
|
|
298
|
+
var TELEGRAM_AUTH_CODE_VERIFIER_PREFIX = "hypurr-connect-auth-code-verifier:";
|
|
299
|
+
var TELEGRAM_AUTH_RETURN_TO_PREFIX = "hypurr-connect-auth-return-to:";
|
|
298
300
|
var TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
|
|
299
301
|
var DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
|
|
300
302
|
var DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
|
|
@@ -430,6 +432,9 @@ function withExpectedFrom(transaction, address) {
|
|
|
430
432
|
function currentReturnTo() {
|
|
431
433
|
const url = new URL(window.location.href);
|
|
432
434
|
for (const param of [
|
|
435
|
+
"code",
|
|
436
|
+
"error",
|
|
437
|
+
"error_description",
|
|
433
438
|
"token",
|
|
434
439
|
"token_type",
|
|
435
440
|
"token_source",
|
|
@@ -440,6 +445,29 @@ function currentReturnTo() {
|
|
|
440
445
|
}
|
|
441
446
|
return url.toString();
|
|
442
447
|
}
|
|
448
|
+
function cleanAuthCallbackUrl() {
|
|
449
|
+
const cleanUrl = new URL(window.location.href);
|
|
450
|
+
for (const param of [
|
|
451
|
+
"code",
|
|
452
|
+
"error",
|
|
453
|
+
"error_description",
|
|
454
|
+
"token",
|
|
455
|
+
"token_type",
|
|
456
|
+
"token_source",
|
|
457
|
+
"state",
|
|
458
|
+
"scope"
|
|
459
|
+
]) {
|
|
460
|
+
cleanUrl.searchParams.delete(param);
|
|
461
|
+
}
|
|
462
|
+
window.history.replaceState({}, document.title, cleanUrl.toString());
|
|
463
|
+
}
|
|
464
|
+
function base64UrlEncode(bytes) {
|
|
465
|
+
let value = "";
|
|
466
|
+
for (const byte of bytes) {
|
|
467
|
+
value += String.fromCharCode(byte);
|
|
468
|
+
}
|
|
469
|
+
return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
470
|
+
}
|
|
443
471
|
function randomState() {
|
|
444
472
|
const bytes = new Uint8Array(16);
|
|
445
473
|
crypto.getRandomValues(bytes);
|
|
@@ -447,6 +475,23 @@ function randomState() {
|
|
|
447
475
|
""
|
|
448
476
|
);
|
|
449
477
|
}
|
|
478
|
+
function randomCodeVerifier() {
|
|
479
|
+
const bytes = new Uint8Array(64);
|
|
480
|
+
crypto.getRandomValues(bytes);
|
|
481
|
+
return base64UrlEncode(bytes);
|
|
482
|
+
}
|
|
483
|
+
async function createCodeChallenge(codeVerifier) {
|
|
484
|
+
if (!crypto.subtle) {
|
|
485
|
+
throw new Error(
|
|
486
|
+
"[HypurrConnect] Web Crypto API is required for Telegram auth."
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
const digest = await crypto.subtle.digest(
|
|
490
|
+
"SHA-256",
|
|
491
|
+
new TextEncoder().encode(codeVerifier)
|
|
492
|
+
);
|
|
493
|
+
return base64UrlEncode(new Uint8Array(digest));
|
|
494
|
+
}
|
|
450
495
|
function normalizeScopes(scope) {
|
|
451
496
|
if (Array.isArray(scope)) return scope.join(" ");
|
|
452
497
|
return scope?.trim() || DEFAULT_TELEGRAM_SCOPES.join(" ");
|
|
@@ -454,12 +499,157 @@ function normalizeScopes(scope) {
|
|
|
454
499
|
function normalizeClientId(clientId) {
|
|
455
500
|
const normalized = typeof clientId === "string" ? clientId.trim() : "";
|
|
456
501
|
if (!normalized) {
|
|
457
|
-
throw new Error("[HypurrConnect] config.
|
|
502
|
+
throw new Error("[HypurrConnect] config.clientId is required.");
|
|
458
503
|
}
|
|
459
504
|
return normalized;
|
|
460
505
|
}
|
|
506
|
+
function codeVerifierStorageKey(state) {
|
|
507
|
+
return `${TELEGRAM_AUTH_CODE_VERIFIER_PREFIX}${state}`;
|
|
508
|
+
}
|
|
509
|
+
function returnToStorageKey(state) {
|
|
510
|
+
return `${TELEGRAM_AUTH_RETURN_TO_PREFIX}${state}`;
|
|
511
|
+
}
|
|
512
|
+
function storeTelegramAuthSession(state, codeVerifier, returnTo) {
|
|
513
|
+
const previousState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
|
|
514
|
+
if (previousState) {
|
|
515
|
+
sessionStorage.removeItem(codeVerifierStorageKey(previousState));
|
|
516
|
+
sessionStorage.removeItem(returnToStorageKey(previousState));
|
|
517
|
+
}
|
|
518
|
+
sessionStorage.setItem(TELEGRAM_AUTH_STATE_KEY, state);
|
|
519
|
+
sessionStorage.setItem(codeVerifierStorageKey(state), codeVerifier);
|
|
520
|
+
sessionStorage.setItem(returnToStorageKey(state), returnTo);
|
|
521
|
+
}
|
|
522
|
+
function clearTelegramAuthSession(state) {
|
|
523
|
+
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
524
|
+
sessionStorage.removeItem(codeVerifierStorageKey(state));
|
|
525
|
+
sessionStorage.removeItem(returnToStorageKey(state));
|
|
526
|
+
}
|
|
527
|
+
function takeTelegramAuthSession(state) {
|
|
528
|
+
const expectedState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
|
|
529
|
+
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
530
|
+
if (!expectedState || state !== expectedState) {
|
|
531
|
+
if (expectedState) {
|
|
532
|
+
sessionStorage.removeItem(codeVerifierStorageKey(expectedState));
|
|
533
|
+
sessionStorage.removeItem(returnToStorageKey(expectedState));
|
|
534
|
+
}
|
|
535
|
+
return null;
|
|
536
|
+
}
|
|
537
|
+
const codeVerifierKey = codeVerifierStorageKey(state);
|
|
538
|
+
const returnToKey = returnToStorageKey(state);
|
|
539
|
+
const codeVerifier = sessionStorage.getItem(codeVerifierKey);
|
|
540
|
+
const returnTo = sessionStorage.getItem(returnToKey);
|
|
541
|
+
sessionStorage.removeItem(codeVerifierKey);
|
|
542
|
+
sessionStorage.removeItem(returnToKey);
|
|
543
|
+
return { codeVerifier, returnTo };
|
|
544
|
+
}
|
|
545
|
+
function fallbackAuthTokenUrl(authHubUrl) {
|
|
546
|
+
const url = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
|
|
547
|
+
url.pathname = "/oauth/token";
|
|
548
|
+
url.search = "";
|
|
549
|
+
url.hash = "";
|
|
550
|
+
return url.toString();
|
|
551
|
+
}
|
|
552
|
+
function authMetadataUrls(authHubUrl) {
|
|
553
|
+
const authUrl = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
|
|
554
|
+
const urls = [
|
|
555
|
+
new URL("/.well-known/oauth-authorization-server", authUrl).toString(),
|
|
556
|
+
new URL("/.well-known/openid-configuration", authUrl).toString()
|
|
557
|
+
];
|
|
558
|
+
const authPath = authUrl.pathname.replace(/\/+$/, "");
|
|
559
|
+
const basePath = authPath.replace(/\/[^/]*$/, "");
|
|
560
|
+
if (basePath) {
|
|
561
|
+
urls.push(
|
|
562
|
+
new URL(
|
|
563
|
+
`/.well-known/oauth-authorization-server${basePath}`,
|
|
564
|
+
authUrl
|
|
565
|
+
).toString()
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
return Array.from(new Set(urls));
|
|
569
|
+
}
|
|
570
|
+
async function tokenUrlFromMetadata(authHubUrl) {
|
|
571
|
+
for (const metadataUrl of authMetadataUrls(authHubUrl)) {
|
|
572
|
+
try {
|
|
573
|
+
const response = await fetch(metadataUrl, {
|
|
574
|
+
headers: { accept: "application/json" }
|
|
575
|
+
});
|
|
576
|
+
if (!response.ok) continue;
|
|
577
|
+
const metadata = await response.json();
|
|
578
|
+
const tokenEndpoint = metadata.token_endpoint;
|
|
579
|
+
if (typeof tokenEndpoint === "string" && tokenEndpoint.trim()) {
|
|
580
|
+
return tokenEndpoint.trim();
|
|
581
|
+
}
|
|
582
|
+
} catch {
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return void 0;
|
|
586
|
+
}
|
|
587
|
+
async function resolveAuthTokenUrl(authHubUrl) {
|
|
588
|
+
return await tokenUrlFromMetadata(authHubUrl) || fallbackAuthTokenUrl(authHubUrl);
|
|
589
|
+
}
|
|
590
|
+
function getTokenFromExchangeResponse(data) {
|
|
591
|
+
if (typeof data === "string") {
|
|
592
|
+
const token = data.trim();
|
|
593
|
+
return token || null;
|
|
594
|
+
}
|
|
595
|
+
if (typeof data !== "object" || data === null) return null;
|
|
596
|
+
const response = data;
|
|
597
|
+
for (const token of [response.token, response.access_token, response.jwt]) {
|
|
598
|
+
if (typeof token === "string" && token.trim()) return token.trim();
|
|
599
|
+
}
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
async function exchangeTelegramAuthCode({
|
|
603
|
+
authHubUrl,
|
|
604
|
+
clientId,
|
|
605
|
+
code,
|
|
606
|
+
codeVerifier,
|
|
607
|
+
returnTo
|
|
608
|
+
}) {
|
|
609
|
+
const body = new URLSearchParams({
|
|
610
|
+
client_id: clientId,
|
|
611
|
+
code,
|
|
612
|
+
code_verifier: codeVerifier,
|
|
613
|
+
grant_type: "authorization_code",
|
|
614
|
+
return_to: returnTo
|
|
615
|
+
});
|
|
616
|
+
const response = await fetch(await resolveAuthTokenUrl(authHubUrl), {
|
|
617
|
+
method: "POST",
|
|
618
|
+
headers: {
|
|
619
|
+
accept: "application/json",
|
|
620
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
621
|
+
},
|
|
622
|
+
body
|
|
623
|
+
});
|
|
624
|
+
const responseText = await response.text();
|
|
625
|
+
if (!response.ok) {
|
|
626
|
+
const detail = responseText.trim();
|
|
627
|
+
throw new Error(
|
|
628
|
+
detail ? `[HypurrConnect] Auth code exchange failed: ${detail}` : `[HypurrConnect] Auth code exchange failed with HTTP ${response.status}.`
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
let responseData = responseText;
|
|
632
|
+
if (responseText) {
|
|
633
|
+
try {
|
|
634
|
+
responseData = JSON.parse(responseText);
|
|
635
|
+
} catch {
|
|
636
|
+
responseData = responseText;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
const token = getTokenFromExchangeResponse(responseData);
|
|
640
|
+
if (!token) {
|
|
641
|
+
throw new Error("[HypurrConnect] Auth code exchange did not return a JWT.");
|
|
642
|
+
}
|
|
643
|
+
return token;
|
|
644
|
+
}
|
|
461
645
|
function isTelegramAuthMessage(data) {
|
|
462
|
-
|
|
646
|
+
if (typeof data !== "object" || data === null) return false;
|
|
647
|
+
if (!("type" in data) || !("state" in data)) return false;
|
|
648
|
+
const message = data;
|
|
649
|
+
const hasToken = typeof message.token === "string";
|
|
650
|
+
const hasCode = typeof message.code === "string";
|
|
651
|
+
const hasError = typeof message.error === "string";
|
|
652
|
+
return message.type === TELEGRAM_AUTH_MESSAGE && typeof message.state === "string" && (hasToken || hasCode || hasError);
|
|
463
653
|
}
|
|
464
654
|
var HypurrConnectContext = createContext(null);
|
|
465
655
|
function useHypurrConnect() {
|
|
@@ -517,6 +707,47 @@ function HypurrConnectProvider({
|
|
|
517
707
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, token);
|
|
518
708
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
519
709
|
}, []);
|
|
710
|
+
const handleTelegramAuthCallback = useCallback(
|
|
711
|
+
(callback) => {
|
|
712
|
+
const authSession = takeTelegramAuthSession(callback.state);
|
|
713
|
+
if (!authSession) {
|
|
714
|
+
setTgError("Invalid auth callback state.");
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
if (callback.error) {
|
|
718
|
+
setTgError(callback.error);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
if (callback.code) {
|
|
722
|
+
if (!authSession.codeVerifier) {
|
|
723
|
+
setTgError("Missing auth code verifier.");
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
setTgLoading(true);
|
|
727
|
+
setTgError(null);
|
|
728
|
+
void exchangeTelegramAuthCode({
|
|
729
|
+
authHubUrl: config.telegram?.authHubUrl,
|
|
730
|
+
clientId: normalizeClientId(config.clientId),
|
|
731
|
+
code: callback.code,
|
|
732
|
+
codeVerifier: authSession.codeVerifier,
|
|
733
|
+
returnTo: authSession.returnTo || currentReturnTo()
|
|
734
|
+
}).then(acceptTelegramToken).catch(
|
|
735
|
+
(err) => setTgError(err instanceof Error ? err.message : String(err))
|
|
736
|
+
).finally(() => setTgLoading(false));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (callback.token) {
|
|
740
|
+
acceptTelegramToken(callback.token);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
setTgError("Invalid auth callback.");
|
|
744
|
+
},
|
|
745
|
+
[
|
|
746
|
+
acceptTelegramToken,
|
|
747
|
+
config.clientId,
|
|
748
|
+
config.telegram?.authHubUrl
|
|
749
|
+
]
|
|
750
|
+
);
|
|
520
751
|
useEffect(() => {
|
|
521
752
|
if (typeof document === "undefined" || !document.fonts) return;
|
|
522
753
|
for (const face of [
|
|
@@ -532,57 +763,37 @@ function HypurrConnectProvider({
|
|
|
532
763
|
useEffect(() => {
|
|
533
764
|
const params = new URLSearchParams(window.location.search);
|
|
534
765
|
const token = params.get("token");
|
|
535
|
-
|
|
766
|
+
const code = params.get("code");
|
|
767
|
+
const error = params.get("error_description") || params.get("error") || void 0;
|
|
768
|
+
if (!token && !code && !error) {
|
|
536
769
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
537
770
|
return;
|
|
538
771
|
}
|
|
539
772
|
const callbackState = params.get("state") ?? "";
|
|
773
|
+
const callback = {
|
|
774
|
+
code: code || void 0,
|
|
775
|
+
error,
|
|
776
|
+
state: callbackState,
|
|
777
|
+
token: token || void 0,
|
|
778
|
+
type: TELEGRAM_AUTH_MESSAGE
|
|
779
|
+
};
|
|
540
780
|
if (window.opener && window.opener !== window) {
|
|
541
|
-
window.opener.postMessage(
|
|
542
|
-
{
|
|
543
|
-
type: TELEGRAM_AUTH_MESSAGE,
|
|
544
|
-
token,
|
|
545
|
-
state: callbackState
|
|
546
|
-
},
|
|
547
|
-
window.location.origin
|
|
548
|
-
);
|
|
781
|
+
window.opener.postMessage(callback, window.location.origin);
|
|
549
782
|
window.close();
|
|
550
783
|
return;
|
|
551
784
|
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
setTgError("Invalid auth callback state.");
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
acceptTelegramToken(token);
|
|
559
|
-
const cleanUrl = new URL(window.location.href);
|
|
560
|
-
for (const param of [
|
|
561
|
-
"token",
|
|
562
|
-
"token_type",
|
|
563
|
-
"token_source",
|
|
564
|
-
"state",
|
|
565
|
-
"scope"
|
|
566
|
-
]) {
|
|
567
|
-
cleanUrl.searchParams.delete(param);
|
|
568
|
-
}
|
|
569
|
-
window.history.replaceState({}, document.title, cleanUrl.toString());
|
|
570
|
-
}, [acceptTelegramToken]);
|
|
785
|
+
cleanAuthCallbackUrl();
|
|
786
|
+
handleTelegramAuthCallback(callback);
|
|
787
|
+
}, [handleTelegramAuthCallback]);
|
|
571
788
|
useEffect(() => {
|
|
572
789
|
function onMessage(event) {
|
|
573
790
|
if (event.origin !== window.location.origin) return;
|
|
574
791
|
if (!isTelegramAuthMessage(event.data)) return;
|
|
575
|
-
|
|
576
|
-
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
577
|
-
if (!expectedState || event.data.state !== expectedState) {
|
|
578
|
-
setTgError("Invalid auth callback state.");
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
acceptTelegramToken(event.data.token);
|
|
792
|
+
handleTelegramAuthCallback(event.data);
|
|
582
793
|
}
|
|
583
794
|
window.addEventListener("message", onMessage);
|
|
584
795
|
return () => window.removeEventListener("message", onMessage);
|
|
585
|
-
}, [
|
|
796
|
+
}, [handleTelegramAuthCallback]);
|
|
586
797
|
useEffect(() => {
|
|
587
798
|
if (!tgAuthToken || !telegramRpcOptions) return;
|
|
588
799
|
let cancelled = false;
|
|
@@ -1317,22 +1528,16 @@ function HypurrConnectProvider({
|
|
|
1317
1528
|
const closeLoginModal = useCallback(() => setLoginModalOpen(false), []);
|
|
1318
1529
|
const loginTelegram = useCallback(() => {
|
|
1319
1530
|
const state = randomState();
|
|
1320
|
-
|
|
1531
|
+
const codeVerifier = randomCodeVerifier();
|
|
1321
1532
|
const configuredReturnTo = config.telegram?.returnTo;
|
|
1322
1533
|
const returnTo = typeof configuredReturnTo === "function" ? configuredReturnTo() : configuredReturnTo || currentReturnTo();
|
|
1323
|
-
|
|
1324
|
-
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL
|
|
1325
|
-
);
|
|
1326
|
-
authUrl.searchParams.set("client_id", normalizeClientId(config.client_id));
|
|
1327
|
-
authUrl.searchParams.set("return_to", returnTo);
|
|
1328
|
-
authUrl.searchParams.set("state", state);
|
|
1329
|
-
authUrl.searchParams.set("scope", normalizeScopes(config.telegram?.scope));
|
|
1534
|
+
storeTelegramAuthSession(state, codeVerifier, returnTo);
|
|
1330
1535
|
const width = 520;
|
|
1331
1536
|
const height = 720;
|
|
1332
1537
|
const left = window.screenX + Math.max(0, (window.outerWidth - width) / 2);
|
|
1333
1538
|
const top = window.screenY + Math.max(0, (window.outerHeight - height) / 2);
|
|
1334
1539
|
const popup = window.open(
|
|
1335
|
-
|
|
1540
|
+
"about:blank",
|
|
1336
1541
|
"hypurr_telegram_auth",
|
|
1337
1542
|
[
|
|
1338
1543
|
`width=${width}`,
|
|
@@ -1343,13 +1548,40 @@ function HypurrConnectProvider({
|
|
|
1343
1548
|
"scrollbars=yes"
|
|
1344
1549
|
].join(",")
|
|
1345
1550
|
);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1551
|
+
void (async () => {
|
|
1552
|
+
try {
|
|
1553
|
+
const authUrl = new URL(
|
|
1554
|
+
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL
|
|
1555
|
+
);
|
|
1556
|
+
authUrl.searchParams.set(
|
|
1557
|
+
"client_id",
|
|
1558
|
+
normalizeClientId(config.clientId)
|
|
1559
|
+
);
|
|
1560
|
+
authUrl.searchParams.set("return_to", returnTo);
|
|
1561
|
+
authUrl.searchParams.set("state", state);
|
|
1562
|
+
authUrl.searchParams.set(
|
|
1563
|
+
"scope",
|
|
1564
|
+
normalizeScopes(config.telegram?.scope)
|
|
1565
|
+
);
|
|
1566
|
+
authUrl.searchParams.set(
|
|
1567
|
+
"code_challenge",
|
|
1568
|
+
await createCodeChallenge(codeVerifier)
|
|
1569
|
+
);
|
|
1570
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1571
|
+
if (popup) {
|
|
1572
|
+
popup.location.assign(authUrl.toString());
|
|
1573
|
+
popup.focus();
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
window.location.assign(authUrl.toString());
|
|
1577
|
+
} catch (err) {
|
|
1578
|
+
clearTelegramAuthSession(state);
|
|
1579
|
+
if (popup && !popup.closed) popup.close();
|
|
1580
|
+
setTgError(err instanceof Error ? err.message : String(err));
|
|
1581
|
+
}
|
|
1582
|
+
})();
|
|
1351
1583
|
}, [
|
|
1352
|
-
config.
|
|
1584
|
+
config.clientId,
|
|
1353
1585
|
config.telegram?.authHubUrl,
|
|
1354
1586
|
config.telegram?.returnTo,
|
|
1355
1587
|
config.telegram?.scope
|