@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/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,25 +289,212 @@ 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(" ");
|
|
264
314
|
}
|
|
265
315
|
|
|
316
|
+
function normalizeClientId(clientId: unknown): string {
|
|
317
|
+
const normalized = typeof clientId === "string" ? clientId.trim() : "";
|
|
318
|
+
if (!normalized) {
|
|
319
|
+
throw new Error("[HypurrConnect] config.clientId is required.");
|
|
320
|
+
}
|
|
321
|
+
return normalized;
|
|
322
|
+
}
|
|
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 resolveAuthTokenUrl(authHubUrl?: string, tokenUrl?: string): string {
|
|
379
|
+
const configuredTokenUrl = tokenUrl?.trim();
|
|
380
|
+
if (configuredTokenUrl) return configuredTokenUrl;
|
|
381
|
+
|
|
382
|
+
const url = new URL(authHubUrl || DEFAULT_AUTH_HUB_URL);
|
|
383
|
+
const pathWithoutTrailingSlash = url.pathname.replace(/\/+$/, "");
|
|
384
|
+
const basePath = pathWithoutTrailingSlash.replace(/\/[^/]*$/, "");
|
|
385
|
+
url.pathname = `${basePath}/token`;
|
|
386
|
+
url.search = "";
|
|
387
|
+
url.hash = "";
|
|
388
|
+
return url.toString();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getTokenFromExchangeResponse(data: unknown): string | null {
|
|
392
|
+
if (typeof data === "string") {
|
|
393
|
+
const token = data.trim();
|
|
394
|
+
return token || null;
|
|
395
|
+
}
|
|
396
|
+
if (typeof data !== "object" || data === null) return null;
|
|
397
|
+
|
|
398
|
+
const response = data as {
|
|
399
|
+
access_token?: unknown;
|
|
400
|
+
jwt?: unknown;
|
|
401
|
+
token?: unknown;
|
|
402
|
+
};
|
|
403
|
+
for (const token of [response.token, response.access_token, response.jwt]) {
|
|
404
|
+
if (typeof token === "string" && token.trim()) return token.trim();
|
|
405
|
+
}
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function exchangeTelegramAuthCode({
|
|
410
|
+
authHubUrl,
|
|
411
|
+
clientId,
|
|
412
|
+
code,
|
|
413
|
+
codeVerifier,
|
|
414
|
+
returnTo,
|
|
415
|
+
tokenUrl,
|
|
416
|
+
}: {
|
|
417
|
+
authHubUrl?: string;
|
|
418
|
+
clientId: string;
|
|
419
|
+
code: string;
|
|
420
|
+
codeVerifier: string;
|
|
421
|
+
returnTo: string;
|
|
422
|
+
tokenUrl?: string;
|
|
423
|
+
}): Promise<string> {
|
|
424
|
+
const body = new URLSearchParams({
|
|
425
|
+
client_id: clientId,
|
|
426
|
+
code,
|
|
427
|
+
code_verifier: codeVerifier,
|
|
428
|
+
grant_type: "authorization_code",
|
|
429
|
+
return_to: returnTo,
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const response = await fetch(resolveAuthTokenUrl(authHubUrl, tokenUrl), {
|
|
433
|
+
method: "POST",
|
|
434
|
+
headers: {
|
|
435
|
+
accept: "application/json",
|
|
436
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
437
|
+
},
|
|
438
|
+
body,
|
|
439
|
+
});
|
|
440
|
+
const responseText = await response.text();
|
|
441
|
+
|
|
442
|
+
if (!response.ok) {
|
|
443
|
+
const detail = responseText.trim();
|
|
444
|
+
throw new Error(
|
|
445
|
+
detail
|
|
446
|
+
? `[HypurrConnect] Auth code exchange failed: ${detail}`
|
|
447
|
+
: `[HypurrConnect] Auth code exchange failed with HTTP ${response.status}.`,
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let responseData: unknown = responseText;
|
|
452
|
+
if (responseText) {
|
|
453
|
+
try {
|
|
454
|
+
responseData = JSON.parse(responseText) as unknown;
|
|
455
|
+
} catch {
|
|
456
|
+
responseData = responseText;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const token = getTokenFromExchangeResponse(responseData);
|
|
461
|
+
if (!token) {
|
|
462
|
+
throw new Error("[HypurrConnect] Auth code exchange did not return a JWT.");
|
|
463
|
+
}
|
|
464
|
+
return token;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
type TelegramAuthCallback = {
|
|
468
|
+
type?: typeof TELEGRAM_AUTH_MESSAGE;
|
|
469
|
+
state: string;
|
|
470
|
+
token?: string;
|
|
471
|
+
code?: string;
|
|
472
|
+
error?: string;
|
|
473
|
+
};
|
|
474
|
+
|
|
266
475
|
function isTelegramAuthMessage(data: unknown): data is {
|
|
267
476
|
type: typeof TELEGRAM_AUTH_MESSAGE;
|
|
268
|
-
token: string;
|
|
269
477
|
state: string;
|
|
478
|
+
token?: string;
|
|
479
|
+
code?: string;
|
|
480
|
+
error?: string;
|
|
270
481
|
} {
|
|
482
|
+
if (typeof data !== "object" || data === null) return false;
|
|
483
|
+
if (!("type" in data) || !("state" in data)) return false;
|
|
484
|
+
const message = data as {
|
|
485
|
+
type: unknown;
|
|
486
|
+
state: unknown;
|
|
487
|
+
token?: unknown;
|
|
488
|
+
code?: unknown;
|
|
489
|
+
error?: unknown;
|
|
490
|
+
};
|
|
491
|
+
const hasToken = typeof message.token === "string";
|
|
492
|
+
const hasCode = typeof message.code === "string";
|
|
493
|
+
const hasError = typeof message.error === "string";
|
|
271
494
|
return (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
"token" in data &&
|
|
276
|
-
"state" in data &&
|
|
277
|
-
(data as { type: unknown }).type === TELEGRAM_AUTH_MESSAGE &&
|
|
278
|
-
typeof (data as { token: unknown }).token === "string" &&
|
|
279
|
-
typeof (data as { state: unknown }).state === "string"
|
|
495
|
+
message.type === TELEGRAM_AUTH_MESSAGE &&
|
|
496
|
+
typeof message.state === "string" &&
|
|
497
|
+
(hasToken || hasCode || hasError)
|
|
280
498
|
);
|
|
281
499
|
}
|
|
282
500
|
|
|
@@ -354,6 +572,58 @@ export function HypurrConnectProvider({
|
|
|
354
572
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
355
573
|
}, []);
|
|
356
574
|
|
|
575
|
+
const handleTelegramAuthCallback = useCallback(
|
|
576
|
+
(callback: TelegramAuthCallback) => {
|
|
577
|
+
const authSession = takeTelegramAuthSession(callback.state);
|
|
578
|
+
if (!authSession) {
|
|
579
|
+
setTgError("Invalid auth callback state.");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
if (callback.error) {
|
|
584
|
+
setTgError(callback.error);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (callback.code) {
|
|
589
|
+
if (!authSession.codeVerifier) {
|
|
590
|
+
setTgError("Missing auth code verifier.");
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
setTgLoading(true);
|
|
595
|
+
setTgError(null);
|
|
596
|
+
void exchangeTelegramAuthCode({
|
|
597
|
+
authHubUrl: config.telegram?.authHubUrl,
|
|
598
|
+
clientId: normalizeClientId(config.clientId),
|
|
599
|
+
code: callback.code,
|
|
600
|
+
codeVerifier: authSession.codeVerifier,
|
|
601
|
+
returnTo: authSession.returnTo || currentReturnTo(),
|
|
602
|
+
tokenUrl: config.telegram?.tokenUrl,
|
|
603
|
+
})
|
|
604
|
+
.then(acceptTelegramToken)
|
|
605
|
+
.catch((err) =>
|
|
606
|
+
setTgError(err instanceof Error ? err.message : String(err)),
|
|
607
|
+
)
|
|
608
|
+
.finally(() => setTgLoading(false));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (callback.token) {
|
|
613
|
+
acceptTelegramToken(callback.token);
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
setTgError("Invalid auth callback.");
|
|
618
|
+
},
|
|
619
|
+
[
|
|
620
|
+
acceptTelegramToken,
|
|
621
|
+
config.clientId,
|
|
622
|
+
config.telegram?.authHubUrl,
|
|
623
|
+
config.telegram?.tokenUrl,
|
|
624
|
+
],
|
|
625
|
+
);
|
|
626
|
+
|
|
357
627
|
// Eagerly fetch the modal fonts on mount. They're declared via @import in the
|
|
358
628
|
// injected stylesheet but browsers only fetch a webfont when visible text
|
|
359
629
|
// first uses it — that lazy fetch makes scores/labels flash the system
|
|
@@ -374,66 +644,44 @@ export function HypurrConnectProvider({
|
|
|
374
644
|
useEffect(() => {
|
|
375
645
|
const params = new URLSearchParams(window.location.search);
|
|
376
646
|
const token = params.get("token");
|
|
377
|
-
|
|
647
|
+
const code = params.get("code");
|
|
648
|
+
const error =
|
|
649
|
+
params.get("error_description") || params.get("error") || undefined;
|
|
650
|
+
if (!token && !code && !error) {
|
|
378
651
|
localStorage.removeItem(LEGACY_TELEGRAM_STORAGE_KEY);
|
|
379
652
|
return;
|
|
380
653
|
}
|
|
381
654
|
|
|
382
655
|
const callbackState = params.get("state") ?? "";
|
|
656
|
+
const callback = {
|
|
657
|
+
code: code || undefined,
|
|
658
|
+
error,
|
|
659
|
+
state: callbackState,
|
|
660
|
+
token: token || undefined,
|
|
661
|
+
type: TELEGRAM_AUTH_MESSAGE,
|
|
662
|
+
} satisfies TelegramAuthCallback;
|
|
383
663
|
|
|
384
664
|
if (window.opener && window.opener !== window) {
|
|
385
|
-
window.opener.postMessage(
|
|
386
|
-
{
|
|
387
|
-
type: TELEGRAM_AUTH_MESSAGE,
|
|
388
|
-
token,
|
|
389
|
-
state: callbackState,
|
|
390
|
-
},
|
|
391
|
-
window.location.origin,
|
|
392
|
-
);
|
|
665
|
+
window.opener.postMessage(callback, window.location.origin);
|
|
393
666
|
window.close();
|
|
394
667
|
return;
|
|
395
668
|
}
|
|
396
669
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
setTgError("Invalid auth callback state.");
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
acceptTelegramToken(token);
|
|
405
|
-
|
|
406
|
-
const cleanUrl = new URL(window.location.href);
|
|
407
|
-
for (const param of [
|
|
408
|
-
"token",
|
|
409
|
-
"token_type",
|
|
410
|
-
"token_source",
|
|
411
|
-
"state",
|
|
412
|
-
"scope",
|
|
413
|
-
]) {
|
|
414
|
-
cleanUrl.searchParams.delete(param);
|
|
415
|
-
}
|
|
416
|
-
window.history.replaceState({}, document.title, cleanUrl.toString());
|
|
417
|
-
}, [acceptTelegramToken]);
|
|
670
|
+
cleanAuthCallbackUrl();
|
|
671
|
+
handleTelegramAuthCallback(callback);
|
|
672
|
+
}, [handleTelegramAuthCallback]);
|
|
418
673
|
|
|
419
674
|
useEffect(() => {
|
|
420
675
|
function onMessage(event: MessageEvent) {
|
|
421
676
|
if (event.origin !== window.location.origin) return;
|
|
422
677
|
if (!isTelegramAuthMessage(event.data)) return;
|
|
423
678
|
|
|
424
|
-
|
|
425
|
-
sessionStorage.removeItem(TELEGRAM_AUTH_STATE_KEY);
|
|
426
|
-
if (!expectedState || event.data.state !== expectedState) {
|
|
427
|
-
setTgError("Invalid auth callback state.");
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
acceptTelegramToken(event.data.token);
|
|
679
|
+
handleTelegramAuthCallback(event.data);
|
|
432
680
|
}
|
|
433
681
|
|
|
434
682
|
window.addEventListener("message", onMessage);
|
|
435
683
|
return () => window.removeEventListener("message", onMessage);
|
|
436
|
-
}, [
|
|
684
|
+
}, [handleTelegramAuthCallback]);
|
|
437
685
|
|
|
438
686
|
useEffect(() => {
|
|
439
687
|
if (!tgAuthToken || !telegramRpcOptions) return;
|
|
@@ -1353,27 +1601,21 @@ export function HypurrConnectProvider({
|
|
|
1353
1601
|
// ── Auth actions ─────────────────────────────────────────────
|
|
1354
1602
|
const loginTelegram = useCallback(() => {
|
|
1355
1603
|
const state = randomState();
|
|
1356
|
-
|
|
1604
|
+
const codeVerifier = randomCodeVerifier();
|
|
1357
1605
|
|
|
1358
1606
|
const configuredReturnTo = config.telegram?.returnTo;
|
|
1359
1607
|
const returnTo =
|
|
1360
1608
|
typeof configuredReturnTo === "function"
|
|
1361
1609
|
? configuredReturnTo()
|
|
1362
1610
|
: configuredReturnTo || currentReturnTo();
|
|
1363
|
-
|
|
1364
|
-
const authUrl = new URL(
|
|
1365
|
-
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL,
|
|
1366
|
-
);
|
|
1367
|
-
authUrl.searchParams.set("return_to", returnTo);
|
|
1368
|
-
authUrl.searchParams.set("state", state);
|
|
1369
|
-
authUrl.searchParams.set("scope", normalizeScopes(config.telegram?.scope));
|
|
1611
|
+
storeTelegramAuthSession(state, codeVerifier, returnTo);
|
|
1370
1612
|
|
|
1371
1613
|
const width = 520;
|
|
1372
1614
|
const height = 720;
|
|
1373
1615
|
const left = window.screenX + Math.max(0, (window.outerWidth - width) / 2);
|
|
1374
1616
|
const top = window.screenY + Math.max(0, (window.outerHeight - height) / 2);
|
|
1375
1617
|
const popup = window.open(
|
|
1376
|
-
|
|
1618
|
+
"about:blank",
|
|
1377
1619
|
"hypurr_telegram_auth",
|
|
1378
1620
|
[
|
|
1379
1621
|
`width=${width}`,
|
|
@@ -1385,13 +1627,42 @@ export function HypurrConnectProvider({
|
|
|
1385
1627
|
].join(","),
|
|
1386
1628
|
);
|
|
1387
1629
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1630
|
+
void (async () => {
|
|
1631
|
+
try {
|
|
1632
|
+
const authUrl = new URL(
|
|
1633
|
+
config.telegram?.authHubUrl || DEFAULT_AUTH_HUB_URL,
|
|
1634
|
+
);
|
|
1635
|
+
authUrl.searchParams.set(
|
|
1636
|
+
"client_id",
|
|
1637
|
+
normalizeClientId(config.clientId),
|
|
1638
|
+
);
|
|
1639
|
+
authUrl.searchParams.set("return_to", returnTo);
|
|
1640
|
+
authUrl.searchParams.set("state", state);
|
|
1641
|
+
authUrl.searchParams.set(
|
|
1642
|
+
"scope",
|
|
1643
|
+
normalizeScopes(config.telegram?.scope),
|
|
1644
|
+
);
|
|
1645
|
+
authUrl.searchParams.set(
|
|
1646
|
+
"code_challenge",
|
|
1647
|
+
await createCodeChallenge(codeVerifier),
|
|
1648
|
+
);
|
|
1649
|
+
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1392
1650
|
|
|
1393
|
-
|
|
1651
|
+
if (popup) {
|
|
1652
|
+
popup.location.assign(authUrl.toString());
|
|
1653
|
+
popup.focus();
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
window.location.assign(authUrl.toString());
|
|
1658
|
+
} catch (err) {
|
|
1659
|
+
clearTelegramAuthSession(state);
|
|
1660
|
+
if (popup && !popup.closed) popup.close();
|
|
1661
|
+
setTgError(err instanceof Error ? err.message : String(err));
|
|
1662
|
+
}
|
|
1663
|
+
})();
|
|
1394
1664
|
}, [
|
|
1665
|
+
config.clientId,
|
|
1395
1666
|
config.telegram?.authHubUrl,
|
|
1396
1667
|
config.telegram?.returnTo,
|
|
1397
1668
|
config.telegram?.scope,
|
package/src/grpc.ts
CHANGED
|
@@ -5,9 +5,13 @@ import type { HypurrConnectConfig } from "./types";
|
|
|
5
5
|
|
|
6
6
|
const DEFAULT_GRPC_URL = "https://grpc.hypurr.fun";
|
|
7
7
|
|
|
8
|
+
function resolveGrpcUrl(config: HypurrConnectConfig): string {
|
|
9
|
+
return config.grpcUrl?.trim() || DEFAULT_GRPC_URL;
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
function createTransport(config: HypurrConnectConfig) {
|
|
9
13
|
return new GrpcWebFetchTransport({
|
|
10
|
-
baseUrl: config
|
|
14
|
+
baseUrl: resolveGrpcUrl(config),
|
|
11
15
|
timeout: config.grpcTimeout ?? 15_000,
|
|
12
16
|
});
|
|
13
17
|
}
|
package/src/types.ts
CHANGED
|
@@ -20,6 +20,8 @@ import type { HyperliquidWallet } from "hypurr-grpc/ts/hypurr/wallet";
|
|
|
20
20
|
// ─── Config ──────────────────────────────────────────────────────
|
|
21
21
|
|
|
22
22
|
export interface HypurrConnectConfig {
|
|
23
|
+
/** Auth hub client identifier. Sent as `client_id` during Telegram login. */
|
|
24
|
+
clientId: string;
|
|
23
25
|
/** gRPC-web base URL. Defaults to https://grpc.hypurr.fun. */
|
|
24
26
|
grpcUrl?: string;
|
|
25
27
|
/** Media base URL for user-uploaded assets. Defaults to https://media.hypurr.fun. */
|
|
@@ -31,6 +33,8 @@ export interface HypurrConnectConfig {
|
|
|
31
33
|
telegram?: {
|
|
32
34
|
/** Auth hub login URL. Defaults to https://auth.hypurr.fun/login. */
|
|
33
35
|
authHubUrl?: string;
|
|
36
|
+
/** Auth hub token exchange URL. Defaults to the auth hub login URL with `/login` replaced by `/token`. */
|
|
37
|
+
tokenUrl?: string;
|
|
34
38
|
/** Optional callback URL. Defaults to the current page without auth query params. */
|
|
35
39
|
returnTo?: string | (() => string);
|
|
36
40
|
/** Requested hub scopes. Defaults to the scopes required by this SDK. */
|