@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/package.json
CHANGED
|
@@ -60,6 +60,9 @@ interface InternalConnectState extends HypurrConnectState {
|
|
|
60
60
|
const TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-jwt";
|
|
61
61
|
const LEGACY_TELEGRAM_STORAGE_KEY = "hypurr-connect-tg-user";
|
|
62
62
|
const TELEGRAM_AUTH_STATE_KEY = "hypurr-connect-auth-state";
|
|
63
|
+
const TELEGRAM_AUTH_CODE_VERIFIER_PREFIX =
|
|
64
|
+
"hypurr-connect-auth-code-verifier:";
|
|
65
|
+
const TELEGRAM_AUTH_RETURN_TO_PREFIX = "hypurr-connect-auth-return-to:";
|
|
63
66
|
const TELEGRAM_AUTH_MESSAGE = "hypurr-connect:telegram-auth";
|
|
64
67
|
const DEFAULT_AUTH_HUB_URL = "https://auth.hypurr.fun/login";
|
|
65
68
|
const DEFAULT_MEDIA_URL = "https://media.hypurr.fun";
|
|
@@ -239,6 +242,9 @@ function withExpectedFrom(
|
|
|
239
242
|
function currentReturnTo(): string {
|
|
240
243
|
const url = new URL(window.location.href);
|
|
241
244
|
for (const param of [
|
|
245
|
+
"code",
|
|
246
|
+
"error",
|
|
247
|
+
"error_description",
|
|
242
248
|
"token",
|
|
243
249
|
"token_type",
|
|
244
250
|
"token_source",
|
|
@@ -250,6 +256,31 @@ function currentReturnTo(): string {
|
|
|
250
256
|
return url.toString();
|
|
251
257
|
}
|
|
252
258
|
|
|
259
|
+
function cleanAuthCallbackUrl(): void {
|
|
260
|
+
const cleanUrl = new URL(window.location.href);
|
|
261
|
+
for (const param of [
|
|
262
|
+
"code",
|
|
263
|
+
"error",
|
|
264
|
+
"error_description",
|
|
265
|
+
"token",
|
|
266
|
+
"token_type",
|
|
267
|
+
"token_source",
|
|
268
|
+
"state",
|
|
269
|
+
"scope",
|
|
270
|
+
]) {
|
|
271
|
+
cleanUrl.searchParams.delete(param);
|
|
272
|
+
}
|
|
273
|
+
window.history.replaceState({}, document.title, cleanUrl.toString());
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function base64UrlEncode(bytes: Uint8Array): string {
|
|
277
|
+
let value = "";
|
|
278
|
+
for (const byte of bytes) {
|
|
279
|
+
value += String.fromCharCode(byte);
|
|
280
|
+
}
|
|
281
|
+
return btoa(value).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
282
|
+
}
|
|
283
|
+
|
|
253
284
|
function randomState(): string {
|
|
254
285
|
const bytes = new Uint8Array(16);
|
|
255
286
|
crypto.getRandomValues(bytes);
|
|
@@ -258,6 +289,25 @@ function randomState(): string {
|
|
|
258
289
|
);
|
|
259
290
|
}
|
|
260
291
|
|
|
292
|
+
function randomCodeVerifier(): string {
|
|
293
|
+
const bytes = new Uint8Array(64);
|
|
294
|
+
crypto.getRandomValues(bytes);
|
|
295
|
+
return base64UrlEncode(bytes);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function createCodeChallenge(codeVerifier: string): Promise<string> {
|
|
299
|
+
if (!crypto.subtle) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
"[HypurrConnect] Web Crypto API is required for Telegram auth.",
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const digest = await crypto.subtle.digest(
|
|
305
|
+
"SHA-256",
|
|
306
|
+
new TextEncoder().encode(codeVerifier),
|
|
307
|
+
);
|
|
308
|
+
return base64UrlEncode(new Uint8Array(digest));
|
|
309
|
+
}
|
|
310
|
+
|
|
261
311
|
function normalizeScopes(scope?: string | string[]): string {
|
|
262
312
|
if (Array.isArray(scope)) return scope.join(" ");
|
|
263
313
|
return scope?.trim() || DEFAULT_TELEGRAM_SCOPES.join(" ");
|
|
@@ -266,25 +316,227 @@ function normalizeScopes(scope?: string | string[]): string {
|
|
|
266
316
|
function normalizeClientId(clientId: unknown): string {
|
|
267
317
|
const normalized = typeof clientId === "string" ? clientId.trim() : "";
|
|
268
318
|
if (!normalized) {
|
|
269
|
-
throw new Error("[HypurrConnect] config.
|
|
319
|
+
throw new Error("[HypurrConnect] config.clientId is required.");
|
|
270
320
|
}
|
|
271
321
|
return normalized;
|
|
272
322
|
}
|
|
273
323
|
|
|
324
|
+
function codeVerifierStorageKey(state: string): string {
|
|
325
|
+
return `${TELEGRAM_AUTH_CODE_VERIFIER_PREFIX}${state}`;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function returnToStorageKey(state: string): string {
|
|
329
|
+
return `${TELEGRAM_AUTH_RETURN_TO_PREFIX}${state}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function storeTelegramAuthSession(
|
|
333
|
+
state: string,
|
|
334
|
+
codeVerifier: string,
|
|
335
|
+
returnTo: string,
|
|
336
|
+
): void {
|
|
337
|
+
const previousState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
|
|
338
|
+
if (previousState) {
|
|
339
|
+
sessionStorage.removeItem(codeVerifierStorageKey(previousState));
|
|
340
|
+
sessionStorage.removeItem(returnToStorageKey(previousState));
|
|
341
|
+
}
|
|
342
|
+
sessionStorage.setItem(TELEGRAM_AUTH_STATE_KEY, state);
|
|
343
|
+
sessionStorage.setItem(codeVerifierStorageKey(state), codeVerifier);
|
|
344
|
+
sessionStorage.setItem(returnToStorageKey(state), returnTo);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function clearTelegramAuthSession(state: string): void {
|
|
348
|
+
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
349
|
+
sessionStorage.removeItem(codeVerifierStorageKey(state));
|
|
350
|
+
sessionStorage.removeItem(returnToStorageKey(state));
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function takeTelegramAuthSession(state: string):
|
|
354
|
+
| {
|
|
355
|
+
codeVerifier: string | null;
|
|
356
|
+
returnTo: string | null;
|
|
357
|
+
}
|
|
358
|
+
| null {
|
|
359
|
+
const expectedState = sessionStorage.getItem(TELEGRAM_AUTH_STATE_KEY);
|
|
360
|
+
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
361
|
+
if (!expectedState || state !== expectedState) {
|
|
362
|
+
if (expectedState) {
|
|
363
|
+
sessionStorage.removeItem(codeVerifierStorageKey(expectedState));
|
|
364
|
+
sessionStorage.removeItem(returnToStorageKey(expectedState));
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const codeVerifierKey = codeVerifierStorageKey(state);
|
|
370
|
+
const returnToKey = returnToStorageKey(state);
|
|
371
|
+
const codeVerifier = sessionStorage.getItem(codeVerifierKey);
|
|
372
|
+
const returnTo = sessionStorage.getItem(returnToKey);
|
|
373
|
+
sessionStorage.removeItem(codeVerifierKey);
|
|
374
|
+
sessionStorage.removeItem(returnToKey);
|
|
375
|
+
return { codeVerifier, returnTo };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function fallbackAuthTokenUrl(authHubUrl?: string): string {
|
|
379
|
+
const url = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
|
|
380
|
+
url.pathname = "/oauth/token";
|
|
381
|
+
url.search = "";
|
|
382
|
+
url.hash = "";
|
|
383
|
+
return url.toString();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function authMetadataUrls(authHubUrl?: string): string[] {
|
|
387
|
+
const authUrl = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
|
|
388
|
+
const urls = [
|
|
389
|
+
new URL("/.well-known/oauth-authorization-server", authUrl).toString(),
|
|
390
|
+
new URL("/.well-known/openid-configuration", authUrl).toString(),
|
|
391
|
+
];
|
|
392
|
+
|
|
393
|
+
const authPath = authUrl.pathname.replace(/\/+$/, "");
|
|
394
|
+
const basePath = authPath.replace(/\/[^/]*$/, "");
|
|
395
|
+
if (basePath) {
|
|
396
|
+
urls.push(
|
|
397
|
+
new URL(
|
|
398
|
+
`/.well-known/oauth-authorization-server${basePath}`,
|
|
399
|
+
authUrl,
|
|
400
|
+
).toString(),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return Array.from(new Set(urls));
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
async function tokenUrlFromMetadata(
|
|
408
|
+
authHubUrl?: string,
|
|
409
|
+
): Promise<string | undefined> {
|
|
410
|
+
for (const metadataUrl of authMetadataUrls(authHubUrl)) {
|
|
411
|
+
try {
|
|
412
|
+
const response = await fetch(metadataUrl, {
|
|
413
|
+
headers: { accept: "application/json" },
|
|
414
|
+
});
|
|
415
|
+
if (!response.ok) continue;
|
|
416
|
+
const metadata = (await response.json()) as { token_endpoint?: unknown };
|
|
417
|
+
const tokenEndpoint = metadata.token_endpoint;
|
|
418
|
+
if (typeof tokenEndpoint === "string" && tokenEndpoint.trim()) {
|
|
419
|
+
return tokenEndpoint.trim();
|
|
420
|
+
}
|
|
421
|
+
} catch {
|
|
422
|
+
// Metadata discovery is best effort; fall back to the conventional route.
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return undefined;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function resolveAuthTokenUrl(authHubUrl?: string): Promise<string> {
|
|
430
|
+
return (
|
|
431
|
+
(await tokenUrlFromMetadata(authHubUrl)) || fallbackAuthTokenUrl(authHubUrl)
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function getTokenFromExchangeResponse(data: unknown): string | null {
|
|
436
|
+
if (typeof data === "string") {
|
|
437
|
+
const token = data.trim();
|
|
438
|
+
return token || null;
|
|
439
|
+
}
|
|
440
|
+
if (typeof data !== "object" || data === null) return null;
|
|
441
|
+
|
|
442
|
+
const response = data as {
|
|
443
|
+
access_token?: unknown;
|
|
444
|
+
jwt?: unknown;
|
|
445
|
+
token?: unknown;
|
|
446
|
+
};
|
|
447
|
+
for (const token of [response.token, response.access_token, response.jwt]) {
|
|
448
|
+
if (typeof token === "string" && token.trim()) return token.trim();
|
|
449
|
+
}
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async function exchangeTelegramAuthCode({
|
|
454
|
+
authHubUrl,
|
|
455
|
+
clientId,
|
|
456
|
+
code,
|
|
457
|
+
codeVerifier,
|
|
458
|
+
returnTo,
|
|
459
|
+
}: {
|
|
460
|
+
authHubUrl?: string;
|
|
461
|
+
clientId: string;
|
|
462
|
+
code: string;
|
|
463
|
+
codeVerifier: string;
|
|
464
|
+
returnTo: string;
|
|
465
|
+
}): Promise<string> {
|
|
466
|
+
const body = new URLSearchParams({
|
|
467
|
+
client_id: clientId,
|
|
468
|
+
code,
|
|
469
|
+
code_verifier: codeVerifier,
|
|
470
|
+
grant_type: "authorization_code",
|
|
471
|
+
return_to: returnTo,
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
const response = await fetch(await resolveAuthTokenUrl(authHubUrl), {
|
|
475
|
+
method: "POST",
|
|
476
|
+
headers: {
|
|
477
|
+
accept: "application/json",
|
|
478
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
479
|
+
},
|
|
480
|
+
body,
|
|
481
|
+
});
|
|
482
|
+
const responseText = await response.text();
|
|
483
|
+
|
|
484
|
+
if (!response.ok) {
|
|
485
|
+
const detail = responseText.trim();
|
|
486
|
+
throw new Error(
|
|
487
|
+
detail
|
|
488
|
+
? `[HypurrConnect] Auth code exchange failed: ${detail}`
|
|
489
|
+
: `[HypurrConnect] Auth code exchange failed with HTTP ${response.status}.`,
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
let responseData: unknown = responseText;
|
|
494
|
+
if (responseText) {
|
|
495
|
+
try {
|
|
496
|
+
responseData = JSON.parse(responseText) as unknown;
|
|
497
|
+
} catch {
|
|
498
|
+
responseData = responseText;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const token = getTokenFromExchangeResponse(responseData);
|
|
503
|
+
if (!token) {
|
|
504
|
+
throw new Error("[HypurrConnect] Auth code exchange did not return a JWT.");
|
|
505
|
+
}
|
|
506
|
+
return token;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
type TelegramAuthCallback = {
|
|
510
|
+
type?: typeof TELEGRAM_AUTH_MESSAGE;
|
|
511
|
+
state: string;
|
|
512
|
+
token?: string;
|
|
513
|
+
code?: string;
|
|
514
|
+
error?: string;
|
|
515
|
+
};
|
|
516
|
+
|
|
274
517
|
function isTelegramAuthMessage(data: unknown): data is {
|
|
275
518
|
type: typeof TELEGRAM_AUTH_MESSAGE;
|
|
276
|
-
token: string;
|
|
277
519
|
state: string;
|
|
520
|
+
token?: string;
|
|
521
|
+
code?: string;
|
|
522
|
+
error?: string;
|
|
278
523
|
} {
|
|
524
|
+
if (typeof data !== "object" || data === null) return false;
|
|
525
|
+
if (!("type" in data) || !("state" in data)) return false;
|
|
526
|
+
const message = data as {
|
|
527
|
+
type: unknown;
|
|
528
|
+
state: unknown;
|
|
529
|
+
token?: unknown;
|
|
530
|
+
code?: unknown;
|
|
531
|
+
error?: unknown;
|
|
532
|
+
};
|
|
533
|
+
const hasToken = typeof message.token === "string";
|
|
534
|
+
const hasCode = typeof message.code === "string";
|
|
535
|
+
const hasError = typeof message.error === "string";
|
|
279
536
|
return (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
"token" in data &&
|
|
284
|
-
"state" in data &&
|
|
285
|
-
(data as { type: unknown }).type === TELEGRAM_AUTH_MESSAGE &&
|
|
286
|
-
typeof (data as { token: unknown }).token === "string" &&
|
|
287
|
-
typeof (data as { state: unknown }).state === "string"
|
|
537
|
+
message.type === TELEGRAM_AUTH_MESSAGE &&
|
|
538
|
+
typeof message.state === "string" &&
|
|
539
|
+
(hasToken || hasCode || hasError)
|
|
288
540
|
);
|
|
289
541
|
}
|
|
290
542
|
|
|
@@ -362,6 +614,56 @@ export function HypurrConnectProvider({
|
|
|
362
614
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
363
615
|
}, []);
|
|
364
616
|
|
|
617
|
+
const handleTelegramAuthCallback = useCallback(
|
|
618
|
+
(callback: TelegramAuthCallback) => {
|
|
619
|
+
const authSession = takeTelegramAuthSession(callback.state);
|
|
620
|
+
if (!authSession) {
|
|
621
|
+
setTgError("Invalid auth callback state.");
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
if (callback.error) {
|
|
626
|
+
setTgError(callback.error);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
if (callback.code) {
|
|
631
|
+
if (!authSession.codeVerifier) {
|
|
632
|
+
setTgError("Missing auth code verifier.");
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
setTgLoading(true);
|
|
637
|
+
setTgError(null);
|
|
638
|
+
void exchangeTelegramAuthCode({
|
|
639
|
+
authHubUrl: config.telegram?.authHubUrl,
|
|
640
|
+
clientId: normalizeClientId(config.clientId),
|
|
641
|
+
code: callback.code,
|
|
642
|
+
codeVerifier: authSession.codeVerifier,
|
|
643
|
+
returnTo: authSession.returnTo || currentReturnTo(),
|
|
644
|
+
})
|
|
645
|
+
.then(acceptTelegramToken)
|
|
646
|
+
.catch((err) =>
|
|
647
|
+
setTgError(err instanceof Error ? err.message : String(err)),
|
|
648
|
+
)
|
|
649
|
+
.finally(() => setTgLoading(false));
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (callback.token) {
|
|
654
|
+
acceptTelegramToken(callback.token);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
setTgError("Invalid auth callback.");
|
|
659
|
+
},
|
|
660
|
+
[
|
|
661
|
+
acceptTelegramToken,
|
|
662
|
+
config.clientId,
|
|
663
|
+
config.telegram?.authHubUrl,
|
|
664
|
+
],
|
|
665
|
+
);
|
|
666
|
+
|
|
365
667
|
// Eagerly fetch the modal fonts on mount. They're declared via @import in the
|
|
366
668
|
// injected stylesheet but browsers only fetch a webfont when visible text
|
|
367
669
|
// first uses it — that lazy fetch makes scores/labels flash the system
|
|
@@ -382,66 +684,44 @@ export function HypurrConnectProvider({
|
|
|
382
684
|
useEffect(() => {
|
|
383
685
|
const params = new URLSearchParams(window.location.search);
|
|
384
686
|
const token = params.get("token");
|
|
385
|
-
|
|
687
|
+
const code = params.get("code");
|
|
688
|
+
const error =
|
|
689
|
+
params.get("error_description") || params.get("error") || undefined;
|
|
690
|
+
if (!token && !code && !error) {
|
|
386
691
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
387
692
|
return;
|
|
388
693
|
}
|
|
389
694
|
|
|
390
695
|
const callbackState = params.get("state") ?? "";
|
|
696
|
+
const callback = {
|
|
697
|
+
code: code || undefined,
|
|
698
|
+
error,
|
|
699
|
+
state: callbackState,
|
|
700
|
+
token: token || undefined,
|
|
701
|
+
type: TELEGRAM_AUTH_MESSAGE,
|
|
702
|
+
} satisfies TelegramAuthCallback;
|
|
391
703
|
|
|
392
704
|
if (window.opener && window.opener !== window) {
|
|
393
|
-
window.opener.postMessage(
|
|
394
|
-
{
|
|
395
|
-
type: TELEGRAM_AUTH_MESSAGE,
|
|
396
|
-
token,
|
|
397
|
-
state: callbackState,
|
|
398
|
-
},
|
|
399
|
-
window.location.origin,
|
|
400
|
-
);
|
|
705
|
+
window.opener.postMessage(callback, window.location.origin);
|
|
401
706
|
window.close();
|
|
402
707
|
return;
|
|
403
708
|
}
|
|
404
709
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
setTgError("Invalid auth callback state.");
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
acceptTelegramToken(token);
|
|
413
|
-
|
|
414
|
-
const cleanUrl = new URL(window.location.href);
|
|
415
|
-
for (const param of [
|
|
416
|
-
"token",
|
|
417
|
-
"token_type",
|
|
418
|
-
"token_source",
|
|
419
|
-
"state",
|
|
420
|
-
"scope",
|
|
421
|
-
]) {
|
|
422
|
-
cleanUrl.searchParams.delete(param);
|
|
423
|
-
}
|
|
424
|
-
window.history.replaceState({}, document.title, cleanUrl.toString());
|
|
425
|
-
}, [acceptTelegramToken]);
|
|
710
|
+
cleanAuthCallbackUrl();
|
|
711
|
+
handleTelegramAuthCallback(callback);
|
|
712
|
+
}, [handleTelegramAuthCallback]);
|
|
426
713
|
|
|
427
714
|
useEffect(() => {
|
|
428
715
|
function onMessage(event: MessageEvent) {
|
|
429
716
|
if (event.origin !== window.location.origin) return;
|
|
430
717
|
if (!isTelegramAuthMessage(event.data)) return;
|
|
431
718
|
|
|
432
|
-
|
|
433
|
-
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
434
|
-
if (!expectedState || event.data.state !== expectedState) {
|
|
435
|
-
setTgError("Invalid auth callback state.");
|
|
436
|
-
return;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
acceptTelegramToken(event.data.token);
|
|
719
|
+
handleTelegramAuthCallback(event.data);
|
|
440
720
|
}
|
|
441
721
|
|
|
442
722
|
window.addEventListener("message", onMessage);
|
|
443
723
|
return () => window.removeEventListener("message", onMessage);
|
|
444
|
-
}, [
|
|
724
|
+
}, [handleTelegramAuthCallback]);
|
|
445
725
|
|
|
446
726
|
useEffect(() => {
|
|
447
727
|
if (!tgAuthToken || !telegramRpcOptions) return;
|
|
@@ -1361,28 +1641,21 @@ export function HypurrConnectProvider({
|
|
|
1361
1641
|
// ── Auth actions ─────────────────────────────────────────────
|
|
1362
1642
|
const loginTelegram = useCallback(() => {
|
|
1363
1643
|
const state = randomState();
|
|
1364
|
-
|
|
1644
|
+
const codeVerifier = randomCodeVerifier();
|
|
1365
1645
|
|
|
1366
1646
|
const configuredReturnTo = config.telegram?.returnTo;
|
|
1367
1647
|
const returnTo =
|
|
1368
1648
|
typeof configuredReturnTo === "function"
|
|
1369
1649
|
? configuredReturnTo()
|
|
1370
1650
|
: configuredReturnTo || currentReturnTo();
|
|
1371
|
-
|
|
1372
|
-
const authUrl = new URL(
|
|
1373
|
-
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL,
|
|
1374
|
-
);
|
|
1375
|
-
authUrl.searchParams.set("client_id", normalizeClientId(config.client_id));
|
|
1376
|
-
authUrl.searchParams.set("return_to", returnTo);
|
|
1377
|
-
authUrl.searchParams.set("state", state);
|
|
1378
|
-
authUrl.searchParams.set("scope", normalizeScopes(config.telegram?.scope));
|
|
1651
|
+
storeTelegramAuthSession(state, codeVerifier, returnTo);
|
|
1379
1652
|
|
|
1380
1653
|
const width = 520;
|
|
1381
1654
|
const height = 720;
|
|
1382
1655
|
const left = window.screenX + Math.max(0, (window.outerWidth - width) / 2);
|
|
1383
1656
|
const top = window.screenY + Math.max(0, (window.outerHeight - height) / 2);
|
|
1384
1657
|
const popup = window.open(
|
|
1385
|
-
|
|
1658
|
+
"about:blank",
|
|
1386
1659
|
"hypurr_telegram_auth",
|
|
1387
1660
|
[
|
|
1388
1661
|
`width=${width}`,
|
|
@@ -1394,14 +1667,42 @@ export function HypurrConnectProvider({
|
|
|
1394
1667
|
].join(","),
|
|
1395
1668
|
);
|
|
1396
1669
|
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1670
|
+
void (async () => {
|
|
1671
|
+
try {
|
|
1672
|
+
const authUrl = new URL(
|
|
1673
|
+
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL,
|
|
1674
|
+
);
|
|
1675
|
+
authUrl.searchParams.set(
|
|
1676
|
+
"client_id",
|
|
1677
|
+
normalizeClientId(config.clientId),
|
|
1678
|
+
);
|
|
1679
|
+
authUrl.searchParams.set("return_to", returnTo);
|
|
1680
|
+
authUrl.searchParams.set("state", state);
|
|
1681
|
+
authUrl.searchParams.set(
|
|
1682
|
+
"scope",
|
|
1683
|
+
normalizeScopes(config.telegram?.scope),
|
|
1684
|
+
);
|
|
1685
|
+
authUrl.searchParams.set(
|
|
1686
|
+
"code_challenge",
|
|
1687
|
+
await createCodeChallenge(codeVerifier),
|
|
1688
|
+
);
|
|
1689
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1690
|
+
|
|
1691
|
+
if (popup) {
|
|
1692
|
+
popup.location.assign(authUrl.toString());
|
|
1693
|
+
popup.focus();
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1401
1696
|
|
|
1402
|
-
|
|
1697
|
+
window.location.assign(authUrl.toString());
|
|
1698
|
+
} catch (err) {
|
|
1699
|
+
clearTelegramAuthSession(state);
|
|
1700
|
+
if (popup && !popup.closed) popup.close();
|
|
1701
|
+
setTgError(err instanceof Error ? err.message : String(err));
|
|
1702
|
+
}
|
|
1703
|
+
})();
|
|
1403
1704
|
}, [
|
|
1404
|
-
config.
|
|
1705
|
+
config.clientId,
|
|
1405
1706
|
config.telegram?.authHubUrl,
|
|
1406
1707
|
config.telegram?.returnTo,
|
|
1407
1708
|
config.telegram?.scope,
|
package/src/types.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
|
|
|
21
21
|
|
|
22
22
|
export interface HypurrConnectConfig {
|
|
23
23
|
/** Auth hub client identifier. Sent as `client_id` during Telegram login. */
|
|
24
|
-
|
|
24
|
+
clientId: string;
|
|
25
25
|
/** gRPC-web base URL. Defaults to https://grpc.hypurr.fun. */
|
|
26
26
|
grpcUrl?: string;
|
|
27
27
|
/** Media base URL for user-uploaded assets. Defaults to https://media.hypurr.fun. */
|