@hfunlabs/hypurr-connect 0.1.21 → 0.1.23
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 +20 -12
- package/dist/index.d.ts +4 -0
- package/dist/index.js +264 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/HypurrConnectProvider.tsx +333 -62
- package/src/grpc.ts +5 -1
- package/src/types.ts +4 -0
package/README.md
CHANGED
|
@@ -44,10 +44,12 @@ 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
|
+
clientId: "your-client-id",
|
|
47
48
|
isTestnet: false,
|
|
48
49
|
grpcUrl: "https://grpc.hypurr.fun",
|
|
49
50
|
telegram: {
|
|
50
51
|
authHubUrl: "https://auth.hypurr.fun/login",
|
|
52
|
+
// tokenUrl defaults to https://auth.hypurr.fun/token
|
|
51
53
|
scope: [
|
|
52
54
|
"telegram:user:read",
|
|
53
55
|
"telegram:wallet:read",
|
|
@@ -118,12 +120,14 @@ function AppShell() {
|
|
|
118
120
|
|
|
119
121
|
```typescript
|
|
120
122
|
interface HypurrConnectConfig {
|
|
123
|
+
clientId: string; // Auth hub client identifier
|
|
121
124
|
grpcUrl?: string; // gRPC-web base URL (default: https://grpc.hypurr.fun)
|
|
122
125
|
mediaUrl?: string; // Media base URL (default: https://media.hypurr.fun)
|
|
123
126
|
grpcTimeout?: number; // Request timeout in ms (default: 15000)
|
|
124
127
|
isTestnet?: boolean; // Use testnet endpoints (default: false)
|
|
125
128
|
telegram: {
|
|
126
129
|
authHubUrl?: string; // Auth hub URL (default: https://auth.hypurr.fun/login)
|
|
130
|
+
tokenUrl?: string; // Token exchange URL (default: auth hub /token)
|
|
127
131
|
returnTo?: string | (() => string); // Callback URL (default: current page)
|
|
128
132
|
scope?: string | string[]; // Requested JWT scopes
|
|
129
133
|
};
|
|
@@ -132,7 +136,8 @@ interface HypurrConnectConfig {
|
|
|
132
136
|
|
|
133
137
|
The SDK no longer renders Telegram's login widget or opens `oauth.telegram.org`
|
|
134
138
|
directly. Telegram login is delegated to the Hypurr auth hub in a popup, and the
|
|
135
|
-
popup posts
|
|
139
|
+
popup posts either a legacy scoped JWT or an authorization code back to the
|
|
140
|
+
original page.
|
|
136
141
|
|
|
137
142
|
### Dependencies
|
|
138
143
|
|
|
@@ -144,17 +149,20 @@ for generated protobuf service clients.
|
|
|
144
149
|
### Telegram Login
|
|
145
150
|
|
|
146
151
|
1. User clicks "Telegram" in the `LoginModal`.
|
|
147
|
-
2. The SDK opens the configured auth hub in a popup with `
|
|
148
|
-
|
|
152
|
+
2. The SDK opens the configured auth hub in a popup with `client_id`,
|
|
153
|
+
`return_to`, `state`, requested `scope`, `code_challenge`, and
|
|
154
|
+
`code_challenge_method=S256`.
|
|
149
155
|
3. The auth hub performs Telegram login and redirects back to `return_to` with
|
|
150
|
-
a scoped JWT.
|
|
151
|
-
4. The popup callback page posts `{ token, state }` to the
|
|
152
|
-
`postMessage` and closes.
|
|
153
|
-
5. The opener validates `state
|
|
154
|
-
|
|
155
|
-
6.
|
|
156
|
+
either a legacy scoped JWT or an authorization code.
|
|
157
|
+
4. The popup callback page posts `{ token, state }` or `{ code, state }` to the
|
|
158
|
+
opener with `postMessage` and closes.
|
|
159
|
+
5. The opener validates `state`. Legacy tokens are stored directly; auth codes
|
|
160
|
+
are exchanged at the configured token URL with the stored PKCE verifier.
|
|
161
|
+
6. The SDK calls the Hypurr gRPC backend with `Authorization: Bearer <jwt>`
|
|
162
|
+
metadata.
|
|
163
|
+
7. An `ExchangeClient` is created with `GrpcExchangeTransport`; exchange
|
|
156
164
|
actions are still signed server-side by the Hypurr backend.
|
|
157
|
-
|
|
165
|
+
8. The JWT session is persisted in localStorage (`hypurr-connect-tg-jwt`).
|
|
158
166
|
|
|
159
167
|
If the popup is blocked, the SDK falls back to a full-page redirect. The
|
|
160
168
|
default `returnTo` is the current page with auth query params removed; custom
|
|
@@ -579,8 +587,8 @@ Creates a gRPC-Web client for the Telegram service.
|
|
|
579
587
|
|
|
580
588
|
Creates a gRPC-Web client for the Static service.
|
|
581
589
|
|
|
582
|
-
Both use `GrpcWebFetchTransport` with `config.grpcUrl`
|
|
583
|
-
`config.grpcTimeout` as `timeout`.
|
|
590
|
+
Both use `GrpcWebFetchTransport` with `config.grpcUrl` or the default gRPC URL
|
|
591
|
+
as `baseUrl`, and `config.grpcTimeout` as `timeout`.
|
|
584
592
|
|
|
585
593
|
## localStorage Keys
|
|
586
594
|
|
package/dist/index.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export { HyperliquidWallet } from 'hypurr-grpc/ts/hypurr/wallet';
|
|
|
14
14
|
import { AbstractViemLocalAccount } from '@hfunlabs/hyperliquid/signing';
|
|
15
15
|
|
|
16
16
|
interface HypurrConnectConfig {
|
|
17
|
+
/** Auth hub client identifier. Sent as `client_id` during Telegram login. */
|
|
18
|
+
clientId: string;
|
|
17
19
|
/** gRPC-web base URL. Defaults to https://grpc.hypurr.fun. */
|
|
18
20
|
grpcUrl?: string;
|
|
19
21
|
/** Media base URL for user-uploaded assets. Defaults to https://media.hypurr.fun. */
|
|
@@ -25,6 +27,8 @@ interface HypurrConnectConfig {
|
|
|
25
27
|
telegram?: {
|
|
26
28
|
/** Auth hub login URL. Defaults to https://auth.hypurr.fun/login. */
|
|
27
29
|
authHubUrl?: string;
|
|
30
|
+
/** Auth hub token exchange URL. Defaults to the auth hub login URL with `/login` replaced by `/token`. */
|
|
31
|
+
tokenUrl?: string;
|
|
28
32
|
/** Optional callback URL. Defaults to the current page without auth query params. */
|
|
29
33
|
returnTo?: string | (() => string);
|
|
30
34
|
/** Requested hub scopes. Defaults to the scopes required by this SDK. */
|
package/dist/index.js
CHANGED
|
@@ -140,9 +140,12 @@ import { GrpcWebFetchTransport } from "@protobuf-ts/grpcweb-transport";
|
|
|
140
140
|
import { StaticClient } from "hypurr-grpc/ts/hypurr/static/static_service.client";
|
|
141
141
|
import { TelegramClient } from "hypurr-grpc/ts/hypurr/telegram/telegram_service.client";
|
|
142
142
|
var DEFAULT_GRPC_URL = "https://grpc.hypurr.fun";
|
|
143
|
+
function resolveGrpcUrl(config) {
|
|
144
|
+
return config.grpcUrl?.trim() || DEFAULT_GRPC_URL;
|
|
145
|
+
}
|
|
143
146
|
function createTransport(config) {
|
|
144
147
|
return new GrpcWebFetchTransport({
|
|
145
|
-
baseUrl: config
|
|
148
|
+
baseUrl: resolveGrpcUrl(config),
|
|
146
149
|
timeout: config.grpcTimeout ?? 15e3
|
|
147
150
|
});
|
|
148
151
|
}
|
|
@@ -292,6 +295,8 @@ import { jsx } from "react/jsx-runtime";
|
|
|
292
295
|
var TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
|
|
293
296
|
var LEGACY_TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
|
|
294
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:";
|
|
295
300
|
var TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
|
|
296
301
|
var DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
|
|
297
302
|
var DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
|
|
@@ -427,6 +432,9 @@ function withExpectedFrom(transaction, address) {
|
|
|
427
432
|
function currentReturnTo() {
|
|
428
433
|
const url = new URL(window.location.href);
|
|
429
434
|
for (const param of [
|
|
435
|
+
"code",
|
|
436
|
+
"error",
|
|
437
|
+
"error_description",
|
|
430
438
|
"token",
|
|
431
439
|
"token_type",
|
|
432
440
|
"token_source",
|
|
@@ -437,6 +445,29 @@ function currentReturnTo() {
|
|
|
437
445
|
}
|
|
438
446
|
return url.toString();
|
|
439
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
|
+
}
|
|
440
471
|
function randomState() {
|
|
441
472
|
const bytes = new Uint8Array(16);
|
|
442
473
|
crypto.getRandomValues(bytes);
|
|
@@ -444,12 +475,148 @@ function randomState() {
|
|
|
444
475
|
""
|
|
445
476
|
);
|
|
446
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
|
+
}
|
|
447
495
|
function normalizeScopes(scope) {
|
|
448
496
|
if (Array.isArray(scope)) return scope.join(" ");
|
|
449
497
|
return scope?.trim() || DEFAULT_TELEGRAM_SCOPES.join(" ");
|
|
450
498
|
}
|
|
499
|
+
function normalizeClientId(clientId) {
|
|
500
|
+
const normalized = typeof clientId === "string" ? clientId.trim() : "";
|
|
501
|
+
if (!normalized) {
|
|
502
|
+
throw new Error("[HypurrConnect] config.clientId is required.");
|
|
503
|
+
}
|
|
504
|
+
return normalized;
|
|
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 resolveAuthTokenUrl(authHubUrl, tokenUrl) {
|
|
546
|
+
const configuredTokenUrl = tokenUrl?.trim();
|
|
547
|
+
if (configuredTokenUrl) return configuredTokenUrl;
|
|
548
|
+
const url = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
|
|
549
|
+
const pathWithoutTrailingSlash = url.pathname.replace(/\/+$/, "");
|
|
550
|
+
const basePath = pathWithoutTrailingSlash.replace(/\/[^/]*$/, "");
|
|
551
|
+
url.pathname = `${basePath}/token`;
|
|
552
|
+
url.search = "";
|
|
553
|
+
url.hash = "";
|
|
554
|
+
return url.toString();
|
|
555
|
+
}
|
|
556
|
+
function getTokenFromExchangeResponse(data) {
|
|
557
|
+
if (typeof data === "string") {
|
|
558
|
+
const token = data.trim();
|
|
559
|
+
return token || null;
|
|
560
|
+
}
|
|
561
|
+
if (typeof data !== "object" || data === null) return null;
|
|
562
|
+
const response = data;
|
|
563
|
+
for (const token of [response.token, response.access_token, response.jwt]) {
|
|
564
|
+
if (typeof token === "string" && token.trim()) return token.trim();
|
|
565
|
+
}
|
|
566
|
+
return null;
|
|
567
|
+
}
|
|
568
|
+
async function exchangeTelegramAuthCode({
|
|
569
|
+
authHubUrl,
|
|
570
|
+
clientId,
|
|
571
|
+
code,
|
|
572
|
+
codeVerifier,
|
|
573
|
+
returnTo,
|
|
574
|
+
tokenUrl
|
|
575
|
+
}) {
|
|
576
|
+
const body = new URLSearchParams({
|
|
577
|
+
client_id: clientId,
|
|
578
|
+
code,
|
|
579
|
+
code_verifier: codeVerifier,
|
|
580
|
+
grant_type: "authorization_code",
|
|
581
|
+
return_to: returnTo
|
|
582
|
+
});
|
|
583
|
+
const response = await fetch(resolveAuthTokenUrl(authHubUrl, tokenUrl), {
|
|
584
|
+
method: "POST",
|
|
585
|
+
headers: {
|
|
586
|
+
accept: "application/json",
|
|
587
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
588
|
+
},
|
|
589
|
+
body
|
|
590
|
+
});
|
|
591
|
+
const responseText = await response.text();
|
|
592
|
+
if (!response.ok) {
|
|
593
|
+
const detail = responseText.trim();
|
|
594
|
+
throw new Error(
|
|
595
|
+
detail ? `[HypurrConnect] Auth code exchange failed: ${detail}` : `[HypurrConnect] Auth code exchange failed with HTTP ${response.status}.`
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
let responseData = responseText;
|
|
599
|
+
if (responseText) {
|
|
600
|
+
try {
|
|
601
|
+
responseData = JSON.parse(responseText);
|
|
602
|
+
} catch {
|
|
603
|
+
responseData = responseText;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const token = getTokenFromExchangeResponse(responseData);
|
|
607
|
+
if (!token) {
|
|
608
|
+
throw new Error("[HypurrConnect] Auth code exchange did not return a JWT.");
|
|
609
|
+
}
|
|
610
|
+
return token;
|
|
611
|
+
}
|
|
451
612
|
function isTelegramAuthMessage(data) {
|
|
452
|
-
|
|
613
|
+
if (typeof data !== "object" || data === null) return false;
|
|
614
|
+
if (!("type" in data) || !("state" in data)) return false;
|
|
615
|
+
const message = data;
|
|
616
|
+
const hasToken = typeof message.token === "string";
|
|
617
|
+
const hasCode = typeof message.code === "string";
|
|
618
|
+
const hasError = typeof message.error === "string";
|
|
619
|
+
return message.type === TELEGRAM_AUTH_MESSAGE && typeof message.state === "string" && (hasToken || hasCode || hasError);
|
|
453
620
|
}
|
|
454
621
|
var HypurrConnectContext = createContext(null);
|
|
455
622
|
function useHypurrConnect() {
|
|
@@ -507,6 +674,49 @@ function HypurrConnectProvider({
|
|
|
507
674
|
localStorage.setItem(TELEGRAM_STORAGE_KEY, token);
|
|
508
675
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
509
676
|
}, []);
|
|
677
|
+
const handleTelegramAuthCallback = useCallback(
|
|
678
|
+
(callback) => {
|
|
679
|
+
const authSession = takeTelegramAuthSession(callback.state);
|
|
680
|
+
if (!authSession) {
|
|
681
|
+
setTgError("Invalid auth callback state.");
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
if (callback.error) {
|
|
685
|
+
setTgError(callback.error);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (callback.code) {
|
|
689
|
+
if (!authSession.codeVerifier) {
|
|
690
|
+
setTgError("Missing auth code verifier.");
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
setTgLoading(true);
|
|
694
|
+
setTgError(null);
|
|
695
|
+
void exchangeTelegramAuthCode({
|
|
696
|
+
authHubUrl: config.telegram?.authHubUrl,
|
|
697
|
+
clientId: normalizeClientId(config.clientId),
|
|
698
|
+
code: callback.code,
|
|
699
|
+
codeVerifier: authSession.codeVerifier,
|
|
700
|
+
returnTo: authSession.returnTo || currentReturnTo(),
|
|
701
|
+
tokenUrl: config.telegram?.tokenUrl
|
|
702
|
+
}).then(acceptTelegramToken).catch(
|
|
703
|
+
(err) => setTgError(err instanceof Error ? err.message : String(err))
|
|
704
|
+
).finally(() => setTgLoading(false));
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (callback.token) {
|
|
708
|
+
acceptTelegramToken(callback.token);
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
setTgError("Invalid auth callback.");
|
|
712
|
+
},
|
|
713
|
+
[
|
|
714
|
+
acceptTelegramToken,
|
|
715
|
+
config.clientId,
|
|
716
|
+
config.telegram?.authHubUrl,
|
|
717
|
+
config.telegram?.tokenUrl
|
|
718
|
+
]
|
|
719
|
+
);
|
|
510
720
|
useEffect(() => {
|
|
511
721
|
if (typeof document === "undefined" || !document.fonts) return;
|
|
512
722
|
for (const face of [
|
|
@@ -522,57 +732,37 @@ function HypurrConnectProvider({
|
|
|
522
732
|
useEffect(() => {
|
|
523
733
|
const params = new URLSearchParams(window.location.search);
|
|
524
734
|
const token = params.get("token");
|
|
525
|
-
|
|
735
|
+
const code = params.get("code");
|
|
736
|
+
const error = params.get("error_description") || params.get("error") || void 0;
|
|
737
|
+
if (!token && !code && !error) {
|
|
526
738
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
527
739
|
return;
|
|
528
740
|
}
|
|
529
741
|
const callbackState = params.get("state") ?? "";
|
|
742
|
+
const callback = {
|
|
743
|
+
code: code || void 0,
|
|
744
|
+
error,
|
|
745
|
+
state: callbackState,
|
|
746
|
+
token: token || void 0,
|
|
747
|
+
type: TELEGRAM_AUTH_MESSAGE
|
|
748
|
+
};
|
|
530
749
|
if (window.opener && window.opener !== window) {
|
|
531
|
-
window.opener.postMessage(
|
|
532
|
-
{
|
|
533
|
-
type: TELEGRAM_AUTH_MESSAGE,
|
|
534
|
-
token,
|
|
535
|
-
state: callbackState
|
|
536
|
-
},
|
|
537
|
-
window.location.origin
|
|
538
|
-
);
|
|
750
|
+
window.opener.postMessage(callback, window.location.origin);
|
|
539
751
|
window.close();
|
|
540
752
|
return;
|
|
541
753
|
}
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
setTgError("Invalid auth callback state.");
|
|
546
|
-
return;
|
|
547
|
-
}
|
|
548
|
-
acceptTelegramToken(token);
|
|
549
|
-
const cleanUrl = new URL(window.location.href);
|
|
550
|
-
for (const param of [
|
|
551
|
-
"token",
|
|
552
|
-
"token_type",
|
|
553
|
-
"token_source",
|
|
554
|
-
"state",
|
|
555
|
-
"scope"
|
|
556
|
-
]) {
|
|
557
|
-
cleanUrl.searchParams.delete(param);
|
|
558
|
-
}
|
|
559
|
-
window.history.replaceState({}, document.title, cleanUrl.toString());
|
|
560
|
-
}, [acceptTelegramToken]);
|
|
754
|
+
cleanAuthCallbackUrl();
|
|
755
|
+
handleTelegramAuthCallback(callback);
|
|
756
|
+
}, [handleTelegramAuthCallback]);
|
|
561
757
|
useEffect(() => {
|
|
562
758
|
function onMessage(event) {
|
|
563
759
|
if (event.origin !== window.location.origin) return;
|
|
564
760
|
if (!isTelegramAuthMessage(event.data)) return;
|
|
565
|
-
|
|
566
|
-
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
567
|
-
if (!expectedState || event.data.state !== expectedState) {
|
|
568
|
-
setTgError("Invalid auth callback state.");
|
|
569
|
-
return;
|
|
570
|
-
}
|
|
571
|
-
acceptTelegramToken(event.data.token);
|
|
761
|
+
handleTelegramAuthCallback(event.data);
|
|
572
762
|
}
|
|
573
763
|
window.addEventListener("message", onMessage);
|
|
574
764
|
return () => window.removeEventListener("message", onMessage);
|
|
575
|
-
}, [
|
|
765
|
+
}, [handleTelegramAuthCallback]);
|
|
576
766
|
useEffect(() => {
|
|
577
767
|
if (!tgAuthToken || !telegramRpcOptions) return;
|
|
578
768
|
let cancelled = false;
|
|
@@ -1307,21 +1497,16 @@ function HypurrConnectProvider({
|
|
|
1307
1497
|
const closeLoginModal = useCallback(() => setLoginModalOpen(false), []);
|
|
1308
1498
|
const loginTelegram = useCallback(() => {
|
|
1309
1499
|
const state = randomState();
|
|
1310
|
-
|
|
1500
|
+
const codeVerifier = randomCodeVerifier();
|
|
1311
1501
|
const configuredReturnTo = config.telegram?.returnTo;
|
|
1312
1502
|
const returnTo = typeof configuredReturnTo === "function" ? configuredReturnTo() : configuredReturnTo || currentReturnTo();
|
|
1313
|
-
|
|
1314
|
-
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL
|
|
1315
|
-
);
|
|
1316
|
-
authUrl.searchParams.set("return_to", returnTo);
|
|
1317
|
-
authUrl.searchParams.set("state", state);
|
|
1318
|
-
authUrl.searchParams.set("scope", normalizeScopes(config.telegram?.scope));
|
|
1503
|
+
storeTelegramAuthSession(state, codeVerifier, returnTo);
|
|
1319
1504
|
const width = 520;
|
|
1320
1505
|
const height = 720;
|
|
1321
1506
|
const left = window.screenX + Math.max(0, (window.outerWidth - width) / 2);
|
|
1322
1507
|
const top = window.screenY + Math.max(0, (window.outerHeight - height) / 2);
|
|
1323
1508
|
const popup = window.open(
|
|
1324
|
-
|
|
1509
|
+
"about:blank",
|
|
1325
1510
|
"hypurr_telegram_auth",
|
|
1326
1511
|
[
|
|
1327
1512
|
`width=${width}`,
|
|
@@ -1332,12 +1517,40 @@ function HypurrConnectProvider({
|
|
|
1332
1517
|
"scrollbars=yes"
|
|
1333
1518
|
].join(",")
|
|
1334
1519
|
);
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1520
|
+
void (async () => {
|
|
1521
|
+
try {
|
|
1522
|
+
const authUrl = new URL(
|
|
1523
|
+
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL
|
|
1524
|
+
);
|
|
1525
|
+
authUrl.searchParams.set(
|
|
1526
|
+
"client_id",
|
|
1527
|
+
normalizeClientId(config.clientId)
|
|
1528
|
+
);
|
|
1529
|
+
authUrl.searchParams.set("return_to", returnTo);
|
|
1530
|
+
authUrl.searchParams.set("state", state);
|
|
1531
|
+
authUrl.searchParams.set(
|
|
1532
|
+
"scope",
|
|
1533
|
+
normalizeScopes(config.telegram?.scope)
|
|
1534
|
+
);
|
|
1535
|
+
authUrl.searchParams.set(
|
|
1536
|
+
"code_challenge",
|
|
1537
|
+
await createCodeChallenge(codeVerifier)
|
|
1538
|
+
);
|
|
1539
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1540
|
+
if (popup) {
|
|
1541
|
+
popup.location.assign(authUrl.toString());
|
|
1542
|
+
popup.focus();
|
|
1543
|
+
return;
|
|
1544
|
+
}
|
|
1545
|
+
window.location.assign(authUrl.toString());
|
|
1546
|
+
} catch (err) {
|
|
1547
|
+
clearTelegramAuthSession(state);
|
|
1548
|
+
if (popup && !popup.closed) popup.close();
|
|
1549
|
+
setTgError(err instanceof Error ? err.message : String(err));
|
|
1550
|
+
}
|
|
1551
|
+
})();
|
|
1340
1552
|
}, [
|
|
1553
|
+
config.clientId,
|
|
1341
1554
|
config.telegram?.authHubUrl,
|
|
1342
1555
|
config.telegram?.returnTo,
|
|
1343
1556
|
config.telegram?.scope
|