@eos3/connect 0.1.4 → 0.1.9

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 CHANGED
@@ -24,28 +24,40 @@ It does not bundle Telegram's script into the main SDK bundle; call
24
24
  ```ts
25
25
  import {
26
26
  createEosConnect,
27
- getEosConnectTelegramWebApp,
28
27
  isEosConnectError,
29
- loadEosConnectTelegramWebAppSdk,
30
28
  normalizeEosConnectError
31
29
  } from '@eos3/connect';
32
30
 
33
- await loadEosConnectTelegramWebAppSdk().catch(() => undefined);
34
- const telegramWebApp = getEosConnectTelegramWebApp();
35
-
36
31
  const eosConnect = createEosConnect({
37
32
  network: 'testnet',
38
33
  apiBaseUrl: 'https://your-wallet-api.example.com',
39
34
  botUsername: 'your_bot',
40
35
  locale: 'zh-CN',
41
- telegramWebApp
36
+ defaultConnectOptions: {
37
+ replaceWallet: true,
38
+ assetLimits: [
39
+ {
40
+ tokenContract: 'core.vaulta',
41
+ symbol: 'A',
42
+ precision: 4,
43
+ perTxLimit: '1',
44
+ dailyLimit: '10',
45
+ totalBudget: '100'
46
+ }
47
+ ]
48
+ }
42
49
  });
43
50
 
44
51
  eosConnect.subscribe((state) => {
45
52
  console.log('EOS wallet state:', state);
46
53
  });
47
54
 
48
- await eosConnect.checkWallet();
55
+ await eosConnect.bootstrapTelegram();
56
+ const quickPay = await eosConnect.checkQuickPay();
57
+
58
+ if (!quickPay.enabled) {
59
+ await eosConnect.enableQuickPay();
60
+ }
49
61
  ```
50
62
 
51
63
  `network` can be `testnet` or `mainnet`. It selects browser signing RPC
@@ -58,13 +70,14 @@ When `botUsername` is set, the SDK sends it as `x-telegram-bot-username` so a
58
70
  shared backend can choose the correct Telegram bot token for `initData`
59
71
  verification.
60
72
 
61
- `connectTelegram()` opens the `bindUrl` returned by the API. The API should
73
+ `enableQuickPay()`, `startTelegramWalletFlow()`, and `connectTelegram()` open the `bindUrl` returned by the API. The API should
62
74
  generate that URL from its `WEB_BASE_URL`, so users leave the Mini App and finish
63
75
  passkey authentication on the hosted passkey binding page.
64
76
 
65
77
  ## Internationalization
66
78
 
67
- The built-in payment confirmation sheet supports English and Simplified Chinese.
79
+ Quick payment labels, Wallet ViewModel text, and the built-in payment confirmation sheet support
80
+ English and Simplified Chinese.
68
81
  The SDK uses explicit `locale`, then Telegram `language_code`, then
69
82
  `navigator.language`, and falls back to English:
70
83
 
@@ -73,12 +86,53 @@ const eosConnect = createEosConnect({
73
86
  telegramWebApp,
74
87
  locale: 'zh-CN',
75
88
  messages: {
89
+ walletSetupTitle: '开启钱包',
76
90
  paymentConfirmTitle: '确认付款',
77
91
  paymentConfirmAction: '支付'
78
92
  }
79
93
  });
80
94
  ```
81
95
 
96
+ ## Quick Payment Flow
97
+
98
+ For app UIs, prefer `checkQuickPay()` and `enableQuickPay()`. They hide
99
+ low-level wallet capability statuses such as `needs_local_key`, open pending
100
+ bind URLs, rebind the current device when the local payment key is missing, and
101
+ can poll until quick payment is enabled:
102
+
103
+ ```ts
104
+ const quickPay = await eosConnect.checkQuickPay();
105
+
106
+ renderQuickPay({
107
+ enabled: quickPay.enabled,
108
+ account: quickPay.account,
109
+ balance: quickPay.balance,
110
+ buttonLabel: quickPay.enabled ? '快捷支付已开启' : '开启快捷支付'
111
+ });
112
+
113
+ if (!quickPay.enabled) {
114
+ const next = await eosConnect.enableQuickPay({
115
+ pollIntervalMs: 1500,
116
+ timeoutMs: 120_000
117
+ });
118
+ renderQuickPay(next);
119
+ }
120
+ ```
121
+
122
+ `enableQuickPay()` performs the common Telegram setup sequence:
123
+
124
+ - loads Telegram's WebApp SDK when needed;
125
+ - initializes `BiometricManager` and checks SecureStorage support;
126
+ - calls `checkWallet()`;
127
+ - opens an existing `pending` `bindUrl`;
128
+ - calls `connectTelegram({ replaceWallet: true })` when the current device is
129
+ missing the local payment key;
130
+ - polls wallet readiness until `ready`, another terminal state, or timeout.
131
+
132
+ Advanced UIs can still call `getWalletView()` or `startTelegramWalletFlow()`
133
+ when they need debug-level states and labels. Pass `waitForReady: false` if
134
+ your UI should return immediately after opening the binding page.
135
+
82
136
  ## Connect a Telegram Wallet
83
137
 
84
138
  ```ts
@@ -220,11 +274,17 @@ await fetch('https://wallet.example.com/api/market/push', {
220
274
  - `client.getProviders()`: returns wallet provider metadata.
221
275
  - `client.getSnapshot()`: returns the current state.
222
276
  - `client.subscribe(listener)`: subscribes to state changes.
277
+ - `client.bootstrapTelegram()`: loads Telegram WebApp SDK, initializes biometrics, and checks secure storage support.
223
278
  - `client.restore()`: loads wallet state from the API.
279
+ - `client.refreshWallet()`: refreshes wallet readiness and returns a UI-ready ViewModel.
224
280
  - `client.checkWallet()`: checks server wallet plus local key readiness.
281
+ - `client.checkQuickPay()`: returns `{ enabled, account, balance, reason }` for business UI.
282
+ - `client.getWalletView()`: returns the current wallet ViewModel for app UIs.
225
283
  - `checkEosConnectTelegramPayStorage(app)`: initializes Telegram biometrics and returns secure storage diagnostics.
226
284
  - `client.connectTelegram(options)`: starts or resumes Telegram binding.
227
285
  - `client.connectTokenPocket(options)`: starts TokenPocket binding.
286
+ - `client.startTelegramWalletFlow(options)`: runs the high-level Telegram wallet setup flow.
287
+ - `client.enableQuickPay(options)`: runs the high-level setup flow and returns quick payment readiness.
228
288
  - `client.pay(options)`: builds, signs, confirms, and pushes a paylimit payment.
229
289
  - `client.disconnect()`: removes the local Telegram payment key from
230
290
  SecureStorage, clears the biometric token, and resets the SDK state.
@@ -0,0 +1,14 @@
1
+ export type EosConnectErrorCode = 'DAILY_LIMIT_EXCEEDED' | 'PER_TX_LIMIT_EXCEEDED' | 'TOTAL_BUDGET_EXCEEDED' | 'INSUFFICIENT_BALANCE' | 'RAM_INSUFFICIENT' | 'PAYMENT_PERMISSION_MISSING' | 'SIGNATURE_REJECTED' | 'SESSION_EXPIRED' | 'NETWORK_ERROR' | 'UNKNOWN_CHAIN_ERROR';
2
+ export declare class EosConnectError extends Error {
3
+ readonly code: EosConnectErrorCode;
4
+ readonly rawMessage: string;
5
+ readonly retryable: boolean;
6
+ constructor(code: EosConnectErrorCode, message: string, options?: {
7
+ rawMessage?: string;
8
+ retryable?: boolean;
9
+ cause?: unknown;
10
+ });
11
+ }
12
+ export declare function isEosConnectError(error: unknown): error is EosConnectError;
13
+ export declare function normalizeEosConnectError(error: unknown): EosConnectError;
14
+ export declare function rawErrorMessage(error: unknown): string;
package/dist/errors.js ADDED
@@ -0,0 +1,143 @@
1
+ export class EosConnectError extends Error {
2
+ code;
3
+ rawMessage;
4
+ retryable;
5
+ constructor(code, message, options = {}) {
6
+ super(message);
7
+ this.name = 'EosConnectError';
8
+ this.code = code;
9
+ this.rawMessage = options.rawMessage ?? message;
10
+ this.retryable = options.retryable ?? false;
11
+ if (options.cause !== undefined) {
12
+ this.cause = options.cause;
13
+ }
14
+ }
15
+ }
16
+ export function isEosConnectError(error) {
17
+ return error instanceof EosConnectError;
18
+ }
19
+ export function normalizeEosConnectError(error) {
20
+ if (isEosConnectError(error)) {
21
+ return error;
22
+ }
23
+ const rawMessage = rawErrorMessage(error);
24
+ const normalized = rawMessage.toLowerCase();
25
+ if (normalized.includes('daily limit exceeded')) {
26
+ return new EosConnectError('DAILY_LIMIT_EXCEEDED', '今日支付额度已用完,请明天再试或提高每日额度。', {
27
+ rawMessage,
28
+ cause: error
29
+ });
30
+ }
31
+ if (normalized.includes('per-transfer limit exceeded') ||
32
+ normalized.includes('per transfer limit exceeded') ||
33
+ normalized.includes('per_tx_limit') ||
34
+ normalized.includes('single transfer limit')) {
35
+ return new EosConnectError('PER_TX_LIMIT_EXCEEDED', '本次支付超过单笔额度,请降低金额或提高单笔额度。', {
36
+ rawMessage,
37
+ cause: error
38
+ });
39
+ }
40
+ if (normalized.includes('total budget exceeded')) {
41
+ return new EosConnectError('TOTAL_BUDGET_EXCEEDED', '该权限的总预算已用完,请调整总预算后再试。', {
42
+ rawMessage,
43
+ cause: error
44
+ });
45
+ }
46
+ if (normalized.includes('overdrawn balance') ||
47
+ normalized.includes('insufficient balance') ||
48
+ normalized.includes('balance is insufficient')) {
49
+ return new EosConnectError('INSUFFICIENT_BALANCE', '余额不足,请充值后再试。', {
50
+ rawMessage,
51
+ cause: error
52
+ });
53
+ }
54
+ if (normalized.includes('ram usage') ||
55
+ normalized.includes('billed ram') ||
56
+ normalized.includes('insufficient ram') ||
57
+ normalized.includes('current ram usage limit')) {
58
+ return new EosConnectError('RAM_INSUFFICIENT', '账号 RAM 不足,请补充 RAM 后再试。', {
59
+ rawMessage,
60
+ cause: error
61
+ });
62
+ }
63
+ if (normalized.includes('bot permission is inactive') ||
64
+ normalized.includes('permission does not match bot authorization') ||
65
+ normalized.includes('paylimit code authority is missing') ||
66
+ normalized.includes('missing from transfer permission') ||
67
+ normalized.includes('transaction declares authority') ||
68
+ normalized.includes('missing authority')) {
69
+ return new EosConnectError('PAYMENT_PERMISSION_MISSING', '快捷支付权限未绑定或已失效,请重新绑定后再试。', {
70
+ rawMessage,
71
+ cause: error
72
+ });
73
+ }
74
+ if (normalized.includes('not signed in') ||
75
+ normalized.includes('invalid session') ||
76
+ normalized.includes('session user not found') ||
77
+ normalized.includes('telegram initdata is missing') ||
78
+ normalized.includes('telegram session not found')) {
79
+ return new EosConnectError('SESSION_EXPIRED', '登录状态已失效,请重新打开小程序或重新登录。', {
80
+ rawMessage,
81
+ cause: error
82
+ });
83
+ }
84
+ if (normalized.includes('notallowederror') ||
85
+ normalized.includes('aborterror') ||
86
+ normalized.includes('payment confirmation was cancelled') ||
87
+ normalized.includes('biometric authentication failed') ||
88
+ normalized.includes('user cancelled')) {
89
+ return new EosConnectError('SIGNATURE_REJECTED', '已取消签名确认,支付未提交。', {
90
+ rawMessage,
91
+ cause: error
92
+ });
93
+ }
94
+ if (normalized.includes('open this wallet in the telegram mobile app') ||
95
+ normalized.includes('secure biometric key storage') ||
96
+ normalized.includes('securestorage is not available') ||
97
+ normalized.includes('secure storage is not available')) {
98
+ return new EosConnectError('SIGNATURE_REJECTED', '请在 Telegram 手机客户端打开,当前设备不支持安全存储。', {
99
+ rawMessage,
100
+ cause: error
101
+ });
102
+ }
103
+ if (normalized.includes('biometric unlock key is missing') ||
104
+ normalized.includes('quick key is missing')) {
105
+ return new EosConnectError('PAYMENT_PERMISSION_MISSING', '当前设备缺少本地支付密钥,请重新绑定。', {
106
+ rawMessage,
107
+ cause: error
108
+ });
109
+ }
110
+ if (normalized.includes('failed to fetch') ||
111
+ normalized.includes('network') ||
112
+ normalized.includes('all eos rpc nodes failed') ||
113
+ normalized.includes('eos rpc') ||
114
+ normalized.includes('timeout')) {
115
+ return new EosConnectError('NETWORK_ERROR', '网络连接失败,请稍后重试。', {
116
+ rawMessage,
117
+ retryable: true,
118
+ cause: error
119
+ });
120
+ }
121
+ return new EosConnectError('UNKNOWN_CHAIN_ERROR', '支付失败,请稍后重试或联系服务方。', {
122
+ rawMessage,
123
+ cause: error
124
+ });
125
+ }
126
+ export function rawErrorMessage(error) {
127
+ if (error instanceof Error) {
128
+ return error.message;
129
+ }
130
+ if (typeof error === 'string') {
131
+ return error;
132
+ }
133
+ if (error && typeof error === 'object') {
134
+ const maybeError = error;
135
+ if (typeof maybeError.error === 'string') {
136
+ return maybeError.error;
137
+ }
138
+ if (typeof maybeError.message === 'string') {
139
+ return maybeError.message;
140
+ }
141
+ }
142
+ return 'EOS Connect request failed';
143
+ }
package/dist/http.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { EosConnectError } from './errors.js';
2
+ export declare function parseResponseBody(response: Response): Promise<unknown>;
3
+ export declare function normalizeHttpResponseError(response: Response, body: unknown): EosConnectError;
package/dist/http.js ADDED
@@ -0,0 +1,40 @@
1
+ import { EosConnectError, normalizeEosConnectError, rawErrorMessage } from './errors.js';
2
+ export async function parseResponseBody(response) {
3
+ try {
4
+ return await response.json();
5
+ }
6
+ catch (jsonError) {
7
+ if (typeof response.text === 'function') {
8
+ const text = await response.text().catch(() => '');
9
+ if (text) {
10
+ return text;
11
+ }
12
+ }
13
+ throw jsonError;
14
+ }
15
+ }
16
+ function httpStatusMessage(response) {
17
+ const status = typeof response.status === 'number' ? response.status : 0;
18
+ const statusText = response.statusText ? ` ${response.statusText}` : '';
19
+ return status ? `HTTP ${status}${statusText}` : 'HTTP request failed';
20
+ }
21
+ function bodyMessage(body) {
22
+ if (typeof body === 'string') {
23
+ return body.replace(/\s+/g, ' ').trim();
24
+ }
25
+ return rawErrorMessage(body);
26
+ }
27
+ export function normalizeHttpResponseError(response, body) {
28
+ if (body && typeof body === 'object') {
29
+ return normalizeEosConnectError(body);
30
+ }
31
+ const rawMessage = `${httpStatusMessage(response)}: ${bodyMessage(body)}`.trim();
32
+ const status = typeof response.status === 'number' ? response.status : 0;
33
+ if (status >= 500) {
34
+ return new EosConnectError('NETWORK_ERROR', '网络连接失败,请稍后重试。', {
35
+ rawMessage,
36
+ retryable: true
37
+ });
38
+ }
39
+ return normalizeEosConnectError(rawMessage);
40
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { PayLimitAssetConfig, PayLimitPayment, SignedTransferPayload } from '@eos3/shared';
2
+ import { type EosConnectTelegramWebApp } from './telegram-web-app.js';
3
+ export { EosConnectError, isEosConnectError, normalizeEosConnectError, type EosConnectErrorCode } from './errors.js';
4
+ export { EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL, getEosConnectTelegramWebApp, loadEosConnectTelegramWebAppSdk, type EosConnectTelegramWebApp } from './telegram-web-app.js';
2
5
  export type EosConnectProviderId = 'telegram' | 'tokenpocket' | 'anchor';
3
6
  export type EosConnectProviderState = 'available' | 'coming_soon' | 'disabled';
4
7
  export type EosConnectStatus = 'idle' | 'not_connected' | 'pending' | 'connected' | 'unsupported' | 'error';
@@ -12,41 +15,6 @@ export interface EosConnectProvider {
12
15
  state: EosConnectProviderState;
13
16
  description: string;
14
17
  }
15
- export interface EosConnectTelegramWebApp {
16
- initData?: string;
17
- initDataUnsafe?: {
18
- user?: {
19
- language_code?: string;
20
- };
21
- };
22
- platform?: string;
23
- version?: string;
24
- openLink?(url: string): void;
25
- SecureStorage?: {
26
- setItem(key: string, value: string, callback?: (error: string | null, isStored?: boolean) => void): void;
27
- getItem(key: string, callback: (error: string | null, value?: string | null) => void): void;
28
- removeItem?(key: string, callback?: (error: string | null, isRemoved?: boolean) => void): void;
29
- };
30
- BiometricManager?: {
31
- isInited?: boolean;
32
- isBiometricAvailable?: boolean;
33
- isAccessRequested?: boolean;
34
- isAccessGranted?: boolean;
35
- isBiometricTokenSaved?: boolean;
36
- init(callback?: () => void): void;
37
- requestAccess?(params: {
38
- reason?: string;
39
- }, callback?: (isGranted: boolean) => void): void;
40
- authenticate(params: {
41
- reason?: string;
42
- }, callback: (isAuthenticated: boolean, biometricToken?: string) => void): void;
43
- updateBiometricToken(token: string, callback?: (isUpdated: boolean) => void): void;
44
- openSettings?(): void;
45
- };
46
- }
47
- export declare const EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL = "https://telegram.org/js/telegram-web-app.js";
48
- export declare function getEosConnectTelegramWebApp(): EosConnectTelegramWebApp | null;
49
- export declare function loadEosConnectTelegramWebAppSdk(doc?: Document | null | undefined): Promise<void>;
50
18
  export interface EosConnectState {
51
19
  status: EosConnectStatus;
52
20
  provider: EosConnectProviderId | null;
@@ -102,9 +70,11 @@ export interface EosConnectOptions {
102
70
  network?: EosConnectNetwork;
103
71
  apiBaseUrl?: string;
104
72
  botUsername?: string;
73
+ tokenPocketSource?: string;
105
74
  deviceLabel?: string;
106
75
  locale?: string;
107
76
  messages?: EosConnectMessages;
77
+ defaultConnectOptions?: EosConnectTelegramOptions;
108
78
  balanceAsset?: EosConnectBalanceAsset;
109
79
  rpcUrls?: string | string[];
110
80
  signTransaction?: EosConnectTransactionSigner;
@@ -127,15 +97,51 @@ export interface EosConnectTelegramOptions {
127
97
  replaceWallet?: boolean;
128
98
  openLink?: boolean;
129
99
  }
100
+ export interface EosConnectWalletFlowOptions extends EosConnectTelegramOptions {
101
+ waitForReady?: boolean;
102
+ pollIntervalMs?: number;
103
+ timeoutMs?: number;
104
+ openExternal?: (url: string) => void;
105
+ }
106
+ export type EosConnectWalletViewStatus = 'setup' | 'pending' | 'ready' | 'unsupported' | 'error';
107
+ export type EosConnectWalletViewAction = 'start_wallet_flow' | 'open_bind_url' | 'pay' | 'none';
108
+ export interface EosConnectWalletViewPrimaryAction {
109
+ label: string;
110
+ action: EosConnectWalletViewAction;
111
+ disabled?: boolean;
112
+ }
113
+ export interface EosConnectWalletView {
114
+ status: EosConnectWalletViewStatus;
115
+ account: string | null;
116
+ balance: string | null;
117
+ canPay: boolean;
118
+ title: string;
119
+ description: string;
120
+ primaryAction: EosConnectWalletViewPrimaryAction;
121
+ bindUrl: string | null;
122
+ reason: string | null;
123
+ }
124
+ export interface EosConnectQuickPayStatus {
125
+ enabled: boolean;
126
+ account: string | null;
127
+ balance: string | null;
128
+ reason: string | null;
129
+ }
130
130
  export interface EosConnectClient {
131
131
  getProviders(): EosConnectProvider[];
132
132
  getSnapshot(): EosConnectState;
133
133
  subscribe(listener: (state: EosConnectState) => void): () => void;
134
+ bootstrapTelegram(): Promise<EosConnectTelegramPayStorageCheck>;
134
135
  restore(): Promise<EosConnectState>;
136
+ refreshWallet(): Promise<EosConnectWalletView>;
135
137
  checkWallet(): Promise<EosConnectWalletCapability>;
138
+ checkQuickPay(): Promise<EosConnectQuickPayStatus>;
139
+ getWalletView(): Promise<EosConnectWalletView>;
136
140
  connect(options: EosConnectConnectOptions): Promise<EosConnectState>;
137
141
  connectTelegram(options?: EosConnectTelegramOptions): Promise<EosConnectState>;
138
142
  connectTokenPocket(options?: EosConnectTelegramOptions): Promise<EosConnectState>;
143
+ startTelegramWalletFlow(options?: EosConnectWalletFlowOptions): Promise<EosConnectWalletView>;
144
+ enableQuickPay(options?: EosConnectWalletFlowOptions): Promise<EosConnectQuickPayStatus>;
139
145
  pay(options: EosConnectPayOptions): Promise<EosConnectPayResult>;
140
146
  disconnect(): Promise<EosConnectState>;
141
147
  }
@@ -169,18 +175,6 @@ export interface EosConnectPayResult {
169
175
  ok: true;
170
176
  txid: string;
171
177
  }
172
- export type EosConnectErrorCode = 'DAILY_LIMIT_EXCEEDED' | 'PER_TX_LIMIT_EXCEEDED' | 'TOTAL_BUDGET_EXCEEDED' | 'INSUFFICIENT_BALANCE' | 'RAM_INSUFFICIENT' | 'PAYMENT_PERMISSION_MISSING' | 'SIGNATURE_REJECTED' | 'SESSION_EXPIRED' | 'NETWORK_ERROR' | 'UNKNOWN_CHAIN_ERROR';
173
- export declare class EosConnectError extends Error {
174
- readonly code: EosConnectErrorCode;
175
- readonly rawMessage: string;
176
- readonly retryable: boolean;
177
- constructor(code: EosConnectErrorCode, message: string, options?: {
178
- rawMessage?: string;
179
- retryable?: boolean;
180
- cause?: unknown;
181
- });
182
- }
183
- export declare function isEosConnectError(error: unknown): error is EosConnectError;
184
178
  export interface EosConnectPaymentDetails {
185
179
  intentId: string;
186
180
  chainId: string;
@@ -194,6 +188,18 @@ export interface EosConnectPaymentDetails {
194
188
  export type EosConnectSignedTransaction = SignedTransferPayload;
195
189
  export type EosConnectPaymentConfirmer = (details: EosConnectPaymentDetails) => boolean | Promise<boolean>;
196
190
  export interface EosConnectMessages {
191
+ walletSetupTitle?: string;
192
+ walletSetupDescription?: string;
193
+ walletReadyTitle?: string;
194
+ walletReadyDescription?: string;
195
+ walletPendingTitle?: string;
196
+ walletPendingDescription?: string;
197
+ walletUnsupportedTitle?: string;
198
+ walletUnsupportedDescription?: string;
199
+ walletErrorTitle?: string;
200
+ walletActionStart?: string;
201
+ walletActionContinue?: string;
202
+ walletActionPay?: string;
197
203
  paymentConfirmTitle?: string;
198
204
  paymentConfirmClose?: string;
199
205
  paymentConfirmRecipient?: string;
@@ -227,8 +233,7 @@ export declare function checkEosConnectTelegramPayStorage(app: EosConnectTelegra
227
233
  export declare function initEosConnectTelegramBiometricManager(app: EosConnectTelegramWebApp, timeoutMs?: number): Promise<boolean>;
228
234
  export declare function openEosConnectTelegramBiometricSettings(app: EosConnectTelegramWebApp): Promise<EosConnectTelegramBiometricSettingsResult>;
229
235
  export declare function eosConnectWalletSetupState(wallet: EosConnectWalletInfo, env?: EosConnectWalletSetupEnv): EosConnectWalletSetupState;
230
- export declare function normalizeEosConnectError(error: unknown): EosConnectError;
231
- export declare function tokenPocketDappUrl(url: string): string;
236
+ export declare function tokenPocketDappUrl(url: string, source?: string): string;
232
237
  export declare function generateEosConnectPaymentKey(): Promise<{
233
238
  privateKey: string;
234
239
  publicKey: string;
package/dist/index.js CHANGED
@@ -1,59 +1,9 @@
1
1
  import { ABI, PrivateKey, Serializer, Transaction } from '@wharfkit/antelope';
2
- export const EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL = 'https://telegram.org/js/telegram-web-app.js';
3
- let telegramWebAppSdkLoadPromise = null;
4
- export function getEosConnectTelegramWebApp() {
5
- const maybeGlobal = globalThis;
6
- return maybeGlobal.window?.Telegram?.WebApp ?? maybeGlobal.Telegram?.WebApp ?? null;
7
- }
8
- export function loadEosConnectTelegramWebAppSdk(doc = globalThis.document) {
9
- if (getEosConnectTelegramWebApp()) {
10
- return Promise.resolve();
11
- }
12
- if (telegramWebAppSdkLoadPromise) {
13
- return telegramWebAppSdkLoadPromise;
14
- }
15
- if (!doc?.head) {
16
- return Promise.reject(new Error('Document is not available to load Telegram WebApp SDK'));
17
- }
18
- telegramWebAppSdkLoadPromise = new Promise((resolve, reject) => {
19
- const finish = () => resolve();
20
- const fail = () => {
21
- telegramWebAppSdkLoadPromise = null;
22
- reject(new Error('Failed to load Telegram WebApp SDK'));
23
- };
24
- const existing = doc.querySelector(`script[src="${EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL}"]`);
25
- if (existing) {
26
- existing.addEventListener('load', finish, { once: true });
27
- existing.addEventListener('error', fail, { once: true });
28
- return;
29
- }
30
- const script = doc.createElement('script');
31
- script.src = EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL;
32
- script.async = true;
33
- script.onload = finish;
34
- script.onerror = fail;
35
- doc.head.appendChild(script);
36
- });
37
- return telegramWebAppSdkLoadPromise;
38
- }
39
- export class EosConnectError extends Error {
40
- code;
41
- rawMessage;
42
- retryable;
43
- constructor(code, message, options = {}) {
44
- super(message);
45
- this.name = 'EosConnectError';
46
- this.code = code;
47
- this.rawMessage = options.rawMessage ?? message;
48
- this.retryable = options.retryable ?? false;
49
- if (options.cause !== undefined) {
50
- this.cause = options.cause;
51
- }
52
- }
53
- }
54
- export function isEosConnectError(error) {
55
- return error instanceof EosConnectError;
56
- }
2
+ import { normalizeHttpResponseError, parseResponseBody } from './http.js';
3
+ import { getEosConnectTelegramWebApp, loadEosConnectTelegramWebAppSdk } from './telegram-web-app.js';
4
+ export { EosConnectError, isEosConnectError, normalizeEosConnectError } from './errors.js';
5
+ import { normalizeEosConnectError } from './errors.js';
6
+ export { EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL, getEosConnectTelegramWebApp, loadEosConnectTelegramWebAppSdk } from './telegram-web-app.js';
57
7
  const defaultWalletSetupEnv = {
58
8
  hasTelegramSession: true,
59
9
  canStorePayKey: true
@@ -437,117 +387,20 @@ function paymentDetailsFromBuiltTransfer(built) {
437
387
  payments
438
388
  };
439
389
  }
440
- export function normalizeEosConnectError(error) {
441
- if (isEosConnectError(error)) {
442
- return error;
443
- }
444
- const rawMessage = rawErrorMessage(error);
445
- const normalized = rawMessage.toLowerCase();
446
- if (normalized.includes('daily limit exceeded')) {
447
- return new EosConnectError('DAILY_LIMIT_EXCEEDED', '今日支付额度已用完,请明天再试或提高每日额度。', {
448
- rawMessage,
449
- cause: error
450
- });
451
- }
452
- if (normalized.includes('per-transfer limit exceeded') ||
453
- normalized.includes('per transfer limit exceeded') ||
454
- normalized.includes('per_tx_limit') ||
455
- normalized.includes('single transfer limit')) {
456
- return new EosConnectError('PER_TX_LIMIT_EXCEEDED', '本次支付超过单笔额度,请降低金额或提高单笔额度。', {
457
- rawMessage,
458
- cause: error
459
- });
460
- }
461
- if (normalized.includes('total budget exceeded')) {
462
- return new EosConnectError('TOTAL_BUDGET_EXCEEDED', '该权限的总预算已用完,请调整总预算后再试。', {
463
- rawMessage,
464
- cause: error
465
- });
466
- }
467
- if (normalized.includes('overdrawn balance') ||
468
- normalized.includes('insufficient balance') ||
469
- normalized.includes('balance is insufficient')) {
470
- return new EosConnectError('INSUFFICIENT_BALANCE', '余额不足,请充值后再试。', {
471
- rawMessage,
472
- cause: error
473
- });
474
- }
475
- if (normalized.includes('ram usage') ||
476
- normalized.includes('billed ram') ||
477
- normalized.includes('insufficient ram') ||
478
- normalized.includes('current ram usage limit')) {
479
- return new EosConnectError('RAM_INSUFFICIENT', '账号 RAM 不足,请补充 RAM 后再试。', {
480
- rawMessage,
481
- cause: error
482
- });
483
- }
484
- if (normalized.includes('bot permission is inactive') ||
485
- normalized.includes('permission does not match bot authorization') ||
486
- normalized.includes('paylimit code authority is missing') ||
487
- normalized.includes('missing from transfer permission') ||
488
- normalized.includes('transaction declares authority') ||
489
- normalized.includes('missing authority')) {
490
- return new EosConnectError('PAYMENT_PERMISSION_MISSING', '快捷支付权限未绑定或已失效,请重新绑定后再试。', {
491
- rawMessage,
492
- cause: error
493
- });
494
- }
495
- if (normalized.includes('not signed in') ||
496
- normalized.includes('invalid session') ||
497
- normalized.includes('session user not found') ||
498
- normalized.includes('telegram initdata is missing') ||
499
- normalized.includes('telegram session not found')) {
500
- return new EosConnectError('SESSION_EXPIRED', '登录状态已失效,请重新打开小程序或重新登录。', {
501
- rawMessage,
502
- cause: error
503
- });
504
- }
505
- if (normalized.includes('notallowederror') ||
506
- normalized.includes('aborterror') ||
507
- normalized.includes('payment confirmation was cancelled') ||
508
- normalized.includes('biometric authentication failed') ||
509
- normalized.includes('user cancelled')) {
510
- return new EosConnectError('SIGNATURE_REJECTED', '已取消签名确认,支付未提交。', {
511
- rawMessage,
512
- cause: error
513
- });
514
- }
515
- if (normalized.includes('failed to fetch') ||
516
- normalized.includes('network') ||
517
- normalized.includes('all eos rpc nodes failed') ||
518
- normalized.includes('eos rpc') ||
519
- normalized.includes('timeout')) {
520
- return new EosConnectError('NETWORK_ERROR', '网络连接失败,请稍后重试。', {
521
- rawMessage,
522
- retryable: true,
523
- cause: error
524
- });
525
- }
526
- return new EosConnectError('UNKNOWN_CHAIN_ERROR', '支付失败,请稍后重试或联系服务方。', {
527
- rawMessage,
528
- cause: error
529
- });
530
- }
531
- function rawErrorMessage(error) {
532
- if (error instanceof Error) {
533
- return error.message;
534
- }
535
- if (typeof error === 'string') {
536
- return error;
537
- }
538
- if (error && typeof error === 'object') {
539
- const maybeError = error;
540
- if (typeof maybeError.error === 'string') {
541
- return maybeError.error;
542
- }
543
- if (typeof maybeError.message === 'string') {
544
- return maybeError.message;
545
- }
546
- }
547
- return 'EOS Connect request failed';
548
- }
549
390
  const EOS_CONNECT_MESSAGES = {
550
391
  en: {
392
+ walletSetupTitle: 'Enable quick payment',
393
+ walletSetupDescription: 'Complete one binding, then eligible payments can be confirmed quickly.',
394
+ walletReadyTitle: 'Wallet connected',
395
+ walletReadyDescription: 'Quick payment is enabled.',
396
+ walletPendingTitle: 'Continue wallet binding',
397
+ walletPendingDescription: 'Finish the security confirmation in the opened binding page.',
398
+ walletUnsupportedTitle: 'Open in Telegram mobile',
399
+ walletUnsupportedDescription: 'Telegram initData and secure storage are required.',
400
+ walletErrorTitle: 'Wallet unavailable',
401
+ walletActionStart: 'Enable quick payment',
402
+ walletActionContinue: 'Continue binding',
403
+ walletActionPay: 'Pay',
551
404
  paymentConfirmTitle: 'Payment details',
552
405
  paymentConfirmClose: 'Close',
553
406
  paymentConfirmRecipient: 'Recipient',
@@ -557,6 +410,18 @@ const EOS_CONNECT_MESSAGES = {
557
410
  emptyMemo: '-'
558
411
  },
559
412
  'zh-CN': {
413
+ walletSetupTitle: '开启快捷支付',
414
+ walletSetupDescription: '完成一次绑定,符合额度的支付可快速确认',
415
+ walletReadyTitle: '钱包已连接',
416
+ walletReadyDescription: '快捷支付已开启',
417
+ walletPendingTitle: '继续完成绑定',
418
+ walletPendingDescription: '请在打开的页面完成安全绑定。',
419
+ walletUnsupportedTitle: '请在 Telegram 手机端打开',
420
+ walletUnsupportedDescription: '需要 Telegram initData 和安全存储能力。',
421
+ walletErrorTitle: '钱包暂不可用',
422
+ walletActionStart: '开启快捷支付',
423
+ walletActionContinue: '继续绑定',
424
+ walletActionPay: '去支付',
560
425
  paymentConfirmTitle: '交易详情',
561
426
  paymentConfirmClose: '关闭',
562
427
  paymentConfirmRecipient: '收款地址',
@@ -696,8 +561,9 @@ function ensurePaymentConfirmStyle(documentRef) {
696
561
  }
697
562
  function defaultConfirmPayment(details, options) {
698
563
  const documentRef = globalThis.document;
699
- if (!documentRef?.body)
700
- return Promise.resolve(true);
564
+ if (!documentRef?.body) {
565
+ return Promise.reject(new Error('Payment confirmation UI is not available. Pass confirmPayment: false to skip it explicitly.'));
566
+ }
701
567
  ensurePaymentConfirmStyle(documentRef);
702
568
  const message = (key) => eosConnectMessage(key, options.locale, options.messages);
703
569
  const backdrop = documentRef.createElement('div');
@@ -775,11 +641,11 @@ function parseRpcUrls(raw) {
775
641
  }
776
642
  return urls;
777
643
  }
778
- export function tokenPocketDappUrl(url) {
644
+ export function tokenPocketDappUrl(url, source = 'eosconnect') {
779
645
  return `tpdapp://open?params=${encodeURIComponent(JSON.stringify({
780
646
  url,
781
647
  chain: 'EOS',
782
- source: 'eospasskey'
648
+ source
783
649
  }))}`;
784
650
  }
785
651
  export async function generateEosConnectPaymentKey() {
@@ -812,11 +678,17 @@ function randomBase64Url(byteLength) {
812
678
  return base64UrlEncode(bytes);
813
679
  }
814
680
  function requireSecureTelegramStorage(app) {
815
- if (!app?.SecureStorage || !app.BiometricManager?.updateBiometricToken) {
681
+ if (!app?.SecureStorage || !app.BiometricManager) {
816
682
  throw new Error(TELEGRAM_MOBILE_REQUIRED);
817
683
  }
818
684
  return app;
819
685
  }
686
+ function requireTelegramMethod(method, name) {
687
+ if (typeof method !== 'function') {
688
+ throw new Error(`Telegram ${name} is not available`);
689
+ }
690
+ return method;
691
+ }
820
692
  async function importUnlockKey(unlockKey) {
821
693
  return crypto.subtle.importKey('raw', arrayBuffer(base64UrlDecode(unlockKey)), { name: 'AES-GCM' }, false, ['encrypt', 'decrypt']);
822
694
  }
@@ -842,9 +714,12 @@ async function decryptPrivateKey(envelope, unlockKey) {
842
714
  }
843
715
  async function ensureBiometricAccess(app) {
844
716
  const biometric = app.BiometricManager;
845
- if (!biometric?.updateBiometricToken) {
717
+ if (!biometric) {
846
718
  throw new Error(TELEGRAM_MOBILE_REQUIRED);
847
719
  }
720
+ requireTelegramMethod(biometric?.init, 'BiometricManager.init');
721
+ requireTelegramMethod(biometric?.authenticate, 'BiometricManager.authenticate');
722
+ requireTelegramMethod(biometric?.updateBiometricToken, 'BiometricManager.updateBiometricToken');
848
723
  if (!biometric.isInited) {
849
724
  await new Promise((resolve) => biometric.init(resolve));
850
725
  }
@@ -864,8 +739,9 @@ async function ensureBiometricAccess(app) {
864
739
  }
865
740
  }
866
741
  function secureStorageSet(app, key, value) {
742
+ const setItem = requireTelegramMethod(app.SecureStorage?.setItem, 'SecureStorage.setItem');
867
743
  return new Promise((resolve, reject) => {
868
- app.SecureStorage?.setItem(key, value, (error, isStored) => {
744
+ setItem.call(app.SecureStorage, key, value, (error, isStored) => {
869
745
  if (error) {
870
746
  reject(new Error(error));
871
747
  return;
@@ -879,8 +755,9 @@ function secureStorageSet(app, key, value) {
879
755
  });
880
756
  }
881
757
  function secureStorageGet(app, key) {
758
+ const getItem = requireTelegramMethod(app.SecureStorage?.getItem, 'SecureStorage.getItem');
882
759
  return new Promise((resolve, reject) => {
883
- app.SecureStorage?.getItem(key, (error, value) => {
760
+ getItem.call(app.SecureStorage, key, (error, value) => {
884
761
  if (error) {
885
762
  reject(new Error(error));
886
763
  return;
@@ -890,8 +767,9 @@ function secureStorageGet(app, key) {
890
767
  });
891
768
  }
892
769
  function updateBiometricToken(app, token) {
770
+ const update = requireTelegramMethod(app.BiometricManager?.updateBiometricToken, 'BiometricManager.updateBiometricToken');
893
771
  return new Promise((resolve, reject) => {
894
- app.BiometricManager?.updateBiometricToken(token, (isUpdated) => {
772
+ update.call(app.BiometricManager, token, (isUpdated) => {
895
773
  if (isUpdated) {
896
774
  resolve();
897
775
  }
@@ -902,8 +780,9 @@ function updateBiometricToken(app, token) {
902
780
  });
903
781
  }
904
782
  function authenticateForUnlock(app) {
783
+ const authenticate = requireTelegramMethod(app.BiometricManager?.authenticate, 'BiometricManager.authenticate');
905
784
  return new Promise((resolve, reject) => {
906
- app.BiometricManager?.authenticate({ reason: 'Unlock EOS signing key' }, (isAuthenticated, biometricToken) => {
785
+ authenticate.call(app.BiometricManager, { reason: 'Unlock EOS signing key' }, (isAuthenticated, biometricToken) => {
907
786
  if (!isAuthenticated) {
908
787
  reject(new Error('Biometric authentication failed'));
909
788
  return;
@@ -932,7 +811,8 @@ function secureStorageRemove(app, key) {
932
811
  });
933
812
  return;
934
813
  }
935
- app.SecureStorage?.setItem(key, '', (error, isStored) => {
814
+ const setItem = requireTelegramMethod(app.SecureStorage?.setItem, 'SecureStorage.setItem');
815
+ setItem.call(app.SecureStorage, key, '', (error, isStored) => {
936
816
  if (error) {
937
817
  reject(new Error(error));
938
818
  return;
@@ -953,7 +833,13 @@ export async function saveEosConnectPaymentKey(privateKey, app) {
953
833
  envelope.publicKey = await publicKeyFromEosPrivateKey(privateKey);
954
834
  envelope.createdAt = new Date().toISOString();
955
835
  await secureStorageSet(telegram, EOS_CONNECT_PRIVATE_KEY_STORAGE, JSON.stringify(envelope));
956
- await updateBiometricToken(telegram, unlockKey);
836
+ try {
837
+ await updateBiometricToken(telegram, unlockKey);
838
+ }
839
+ catch (error) {
840
+ await secureStorageRemove(telegram, EOS_CONNECT_PRIVATE_KEY_STORAGE).catch(() => undefined);
841
+ throw error;
842
+ }
957
843
  }
958
844
  export async function loadEosConnectStoredPublicKey(app) {
959
845
  if (!app?.SecureStorage) {
@@ -1251,6 +1137,146 @@ function notConnectedWalletCapability(reason = null) {
1251
1137
  wallet: null
1252
1138
  };
1253
1139
  }
1140
+ function pendingWalletCapabilityFromState(state) {
1141
+ return {
1142
+ status: 'pending',
1143
+ hasWallet: Boolean(state.hasWallet),
1144
+ canUse: false,
1145
+ needsLocalKey: false,
1146
+ account: state.account,
1147
+ balance: state.balance,
1148
+ bindId: state.bindId,
1149
+ bindUrl: state.bindUrl,
1150
+ expectedPublicKey: null,
1151
+ localPublicKey: null,
1152
+ reason: state.error,
1153
+ wallet: null
1154
+ };
1155
+ }
1156
+ function walletViewFromCapability(capability, message) {
1157
+ if (capability.status === 'ready') {
1158
+ return {
1159
+ status: 'ready',
1160
+ account: capability.account,
1161
+ balance: capability.balance,
1162
+ canPay: true,
1163
+ title: message('walletReadyTitle'),
1164
+ description: message('walletReadyDescription'),
1165
+ primaryAction: {
1166
+ label: message('walletActionPay'),
1167
+ action: 'pay'
1168
+ },
1169
+ bindUrl: null,
1170
+ reason: null
1171
+ };
1172
+ }
1173
+ if (capability.status === 'pending') {
1174
+ return {
1175
+ status: 'pending',
1176
+ account: capability.account,
1177
+ balance: capability.balance,
1178
+ canPay: false,
1179
+ title: message('walletPendingTitle'),
1180
+ description: capability.reason ?? message('walletPendingDescription'),
1181
+ primaryAction: {
1182
+ label: message('walletActionContinue'),
1183
+ action: capability.bindUrl ? 'open_bind_url' : 'none',
1184
+ disabled: !capability.bindUrl
1185
+ },
1186
+ bindUrl: capability.bindUrl,
1187
+ reason: capability.reason
1188
+ };
1189
+ }
1190
+ if (capability.status === 'unsupported') {
1191
+ return {
1192
+ status: 'unsupported',
1193
+ account: null,
1194
+ balance: capability.balance,
1195
+ canPay: false,
1196
+ title: message('walletUnsupportedTitle'),
1197
+ description: capability.reason ?? message('walletUnsupportedDescription'),
1198
+ primaryAction: {
1199
+ label: message('walletActionStart'),
1200
+ action: 'none',
1201
+ disabled: true
1202
+ },
1203
+ bindUrl: null,
1204
+ reason: capability.reason
1205
+ };
1206
+ }
1207
+ return {
1208
+ status: 'setup',
1209
+ account: capability.account,
1210
+ balance: capability.balance,
1211
+ canPay: false,
1212
+ title: message('walletSetupTitle'),
1213
+ description: message('walletSetupDescription'),
1214
+ primaryAction: {
1215
+ label: message('walletActionStart'),
1216
+ action: 'start_wallet_flow'
1217
+ },
1218
+ bindUrl: capability.bindUrl,
1219
+ reason: capability.reason
1220
+ };
1221
+ }
1222
+ function walletViewFromError(error, message) {
1223
+ return {
1224
+ status: 'error',
1225
+ account: null,
1226
+ balance: null,
1227
+ canPay: false,
1228
+ title: message('walletErrorTitle'),
1229
+ description: error.message,
1230
+ primaryAction: {
1231
+ label: message('walletActionStart'),
1232
+ action: 'start_wallet_flow'
1233
+ },
1234
+ bindUrl: null,
1235
+ reason: error.rawMessage
1236
+ };
1237
+ }
1238
+ function quickPayFromCapability(capability) {
1239
+ return {
1240
+ enabled: capability.status === 'ready' && capability.canUse,
1241
+ account: capability.account,
1242
+ balance: capability.balance,
1243
+ reason: capability.status === 'ready' && capability.canUse ? null : capability.reason
1244
+ };
1245
+ }
1246
+ function quickPayFromView(view) {
1247
+ return {
1248
+ enabled: view.canPay,
1249
+ account: view.account,
1250
+ balance: view.balance,
1251
+ reason: view.canPay ? null : view.reason ?? view.description
1252
+ };
1253
+ }
1254
+ function quickPayFromError(error) {
1255
+ return {
1256
+ enabled: false,
1257
+ account: null,
1258
+ balance: null,
1259
+ reason: error.message
1260
+ };
1261
+ }
1262
+ function mergeTelegramConnectOptions(defaults, overrides) {
1263
+ return {
1264
+ botUsername: overrides?.botUsername ?? defaults?.botUsername,
1265
+ assetLimits: overrides?.assetLimits ?? defaults?.assetLimits,
1266
+ replaceWallet: overrides?.replaceWallet ?? defaults?.replaceWallet,
1267
+ openLink: overrides?.openLink ?? defaults?.openLink
1268
+ };
1269
+ }
1270
+ function delay(ms) {
1271
+ return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
1272
+ }
1273
+ function openFlowUrl(url, clientOptions, flowOptions, telegramWebApp) {
1274
+ if (flowOptions.openExternal) {
1275
+ flowOptions.openExternal(url);
1276
+ return;
1277
+ }
1278
+ openExternalUrl(url, clientOptions, telegramWebApp);
1279
+ }
1254
1280
  export function createEosConnect(options) {
1255
1281
  const fetchImpl = options.fetch ?? fetch;
1256
1282
  const networkConfig = options.network ? EOS_CONNECT_NETWORKS[options.network] : null;
@@ -1270,6 +1296,9 @@ export function createEosConnect(options) {
1270
1296
  function initData() {
1271
1297
  return telegramWebApp()?.initData ?? '';
1272
1298
  }
1299
+ function message(key) {
1300
+ return eosConnectMessage(key, resolveEosConnectLocale(options.locale, telegramWebApp()), options.messages);
1301
+ }
1273
1302
  function botUsernameHeader() {
1274
1303
  return options.botUsername ? { 'x-telegram-bot-username': options.botUsername } : {};
1275
1304
  }
@@ -1288,9 +1317,15 @@ export function createEosConnect(options) {
1288
1317
  catch (error) {
1289
1318
  throw normalizeEosConnectError(error);
1290
1319
  }
1291
- const body = (await response.json());
1320
+ let body;
1321
+ try {
1322
+ body = await parseResponseBody(response);
1323
+ }
1324
+ catch (error) {
1325
+ throw normalizeEosConnectError(error);
1326
+ }
1292
1327
  if (!response.ok) {
1293
- throw normalizeEosConnectError(body);
1328
+ throw normalizeHttpResponseError(response, body);
1294
1329
  }
1295
1330
  return body;
1296
1331
  }
@@ -1305,6 +1340,31 @@ export function createEosConnect(options) {
1305
1340
  listeners.add(listener);
1306
1341
  return () => listeners.delete(listener);
1307
1342
  },
1343
+ async bootstrapTelegram() {
1344
+ await loadEosConnectTelegramWebAppSdk().catch(() => undefined);
1345
+ const app = telegramWebApp();
1346
+ if (!app) {
1347
+ publish({
1348
+ ...idleState,
1349
+ status: 'unsupported',
1350
+ error: 'Telegram WebApp is missing'
1351
+ });
1352
+ return {
1353
+ supported: false,
1354
+ biometricInitialized: false,
1355
+ diagnostics: eosConnectTelegramPayStorageDiagnostics({})
1356
+ };
1357
+ }
1358
+ const check = await checkEosConnectTelegramPayStorage(app);
1359
+ if (!check.supported) {
1360
+ publish({
1361
+ ...idleState,
1362
+ status: 'unsupported',
1363
+ error: TELEGRAM_MOBILE_REQUIRED
1364
+ });
1365
+ }
1366
+ return check;
1367
+ },
1308
1368
  async restore() {
1309
1369
  if (!initData()) {
1310
1370
  return publish({
@@ -1320,6 +1380,20 @@ export function createEosConnect(options) {
1320
1380
  });
1321
1381
  return publish(walletToState(wallet));
1322
1382
  },
1383
+ async refreshWallet() {
1384
+ try {
1385
+ return walletViewFromCapability(await this.checkWallet(), message);
1386
+ }
1387
+ catch (error) {
1388
+ const normalized = normalizeEosConnectError(error);
1389
+ publish({
1390
+ ...state,
1391
+ status: 'error',
1392
+ error: normalized.message
1393
+ });
1394
+ return walletViewFromError(normalized, message);
1395
+ }
1396
+ },
1323
1397
  async checkWallet() {
1324
1398
  if (!initData()) {
1325
1399
  const capability = {
@@ -1354,6 +1428,23 @@ export function createEosConnect(options) {
1354
1428
  publish(walletCapabilityToState(capability));
1355
1429
  return capability;
1356
1430
  },
1431
+ async checkQuickPay() {
1432
+ try {
1433
+ return quickPayFromCapability(await this.checkWallet());
1434
+ }
1435
+ catch (error) {
1436
+ const normalized = normalizeEosConnectError(error);
1437
+ publish({
1438
+ ...state,
1439
+ status: 'error',
1440
+ error: normalized.message
1441
+ });
1442
+ return quickPayFromError(normalized);
1443
+ }
1444
+ },
1445
+ async getWalletView() {
1446
+ return this.refreshWallet();
1447
+ },
1357
1448
  async connect(connectOptions) {
1358
1449
  if (connectOptions.provider !== 'telegram' && connectOptions.provider !== 'tokenpocket') {
1359
1450
  throw new Error(`${providerDisplayName(connectOptions.provider)} is not supported yet`);
@@ -1409,18 +1500,21 @@ export function createEosConnect(options) {
1409
1500
  error: null
1410
1501
  });
1411
1502
  if (connectOptions.openLink !== false) {
1412
- openExternalUrl(connectOptions.provider === 'tokenpocket' ? tokenPocketDappUrl(bind.bindUrl) : bind.bindUrl, options, telegramWebApp());
1503
+ openExternalUrl(connectOptions.provider === 'tokenpocket'
1504
+ ? tokenPocketDappUrl(bind.bindUrl, options.tokenPocketSource)
1505
+ : bind.bindUrl, options, telegramWebApp());
1413
1506
  }
1414
1507
  return next;
1415
1508
  },
1416
1509
  async connectTelegram(telegramOptions = {}) {
1510
+ const resolvedTelegramOptions = mergeTelegramConnectOptions(options.defaultConnectOptions, telegramOptions);
1417
1511
  const paymentKey = await generateEosConnectPaymentKey();
1418
1512
  const next = await this.connect({
1419
1513
  provider: 'telegram',
1420
1514
  publicKey: paymentKey.publicKey,
1421
- botUsername: telegramOptions.botUsername,
1422
- assetLimits: telegramOptions.assetLimits,
1423
- replaceWallet: telegramOptions.replaceWallet,
1515
+ botUsername: resolvedTelegramOptions.botUsername,
1516
+ assetLimits: resolvedTelegramOptions.assetLimits,
1517
+ replaceWallet: resolvedTelegramOptions.replaceWallet,
1424
1518
  openLink: false
1425
1519
  });
1426
1520
  if (next.hasWallet) {
@@ -1432,11 +1526,75 @@ export function createEosConnect(options) {
1432
1526
  if (!next.reused) {
1433
1527
  await saveEosConnectPaymentKey(paymentKey.privateKey, telegramWebApp());
1434
1528
  }
1435
- if (telegramOptions.openLink !== false) {
1529
+ if (resolvedTelegramOptions.openLink !== false) {
1436
1530
  openExternalUrl(next.bindUrl, options, telegramWebApp());
1437
1531
  }
1438
1532
  return next;
1439
1533
  },
1534
+ async startTelegramWalletFlow(flowOptions = {}) {
1535
+ try {
1536
+ const storage = await this.bootstrapTelegram();
1537
+ if (!storage.supported) {
1538
+ return walletViewFromCapability({
1539
+ ...notConnectedWalletCapability(state.error ?? TELEGRAM_MOBILE_REQUIRED),
1540
+ status: 'unsupported'
1541
+ }, message);
1542
+ }
1543
+ const resolvedTelegramOptions = mergeTelegramConnectOptions(options.defaultConnectOptions, flowOptions);
1544
+ let capability = await this.checkWallet();
1545
+ let openedView = null;
1546
+ if (capability.status === 'ready' || capability.status === 'unsupported') {
1547
+ return walletViewFromCapability(capability, message);
1548
+ }
1549
+ if (capability.status === 'pending') {
1550
+ if (capability.bindUrl && resolvedTelegramOptions.openLink !== false) {
1551
+ openFlowUrl(capability.bindUrl, options, flowOptions, telegramWebApp());
1552
+ }
1553
+ openedView = walletViewFromCapability(capability, message);
1554
+ }
1555
+ else {
1556
+ const next = await this.connectTelegram({
1557
+ ...resolvedTelegramOptions,
1558
+ replaceWallet: capability.needsLocalKey ? true : resolvedTelegramOptions.replaceWallet,
1559
+ openLink: flowOptions.openExternal ? false : resolvedTelegramOptions.openLink
1560
+ });
1561
+ if (next.status === 'pending') {
1562
+ if (next.bindUrl && flowOptions.openExternal) {
1563
+ flowOptions.openExternal(next.bindUrl);
1564
+ }
1565
+ openedView = walletViewFromCapability(pendingWalletCapabilityFromState(next), message);
1566
+ }
1567
+ }
1568
+ if (flowOptions.waitForReady === false) {
1569
+ return openedView ?? this.refreshWallet();
1570
+ }
1571
+ const timeoutMs = flowOptions.timeoutMs ?? 120000;
1572
+ const pollIntervalMs = flowOptions.pollIntervalMs ?? 1500;
1573
+ const deadline = Date.now() + Math.max(0, timeoutMs);
1574
+ do {
1575
+ capability = await this.checkWallet();
1576
+ if (capability.status !== 'pending') {
1577
+ return walletViewFromCapability(capability, message);
1578
+ }
1579
+ if (Date.now() >= deadline) {
1580
+ return walletViewFromCapability(capability, message);
1581
+ }
1582
+ await delay(pollIntervalMs);
1583
+ } while (true);
1584
+ }
1585
+ catch (error) {
1586
+ const normalized = normalizeEosConnectError(error);
1587
+ publish({
1588
+ ...state,
1589
+ status: 'error',
1590
+ error: normalized.message
1591
+ });
1592
+ return walletViewFromError(normalized, message);
1593
+ }
1594
+ },
1595
+ async enableQuickPay(flowOptions = {}) {
1596
+ return quickPayFromView(await this.startTelegramWalletFlow(flowOptions));
1597
+ },
1440
1598
  async connectTokenPocket(tokenPocketOptions = {}) {
1441
1599
  const paymentKey = await generateEosConnectPaymentKey();
1442
1600
  const next = await this.connect({
@@ -1450,7 +1608,7 @@ export function createEosConnect(options) {
1450
1608
  }
1451
1609
  await saveEosConnectPaymentKey(paymentKey.privateKey, telegramWebApp());
1452
1610
  if (tokenPocketOptions.openLink !== false) {
1453
- openExternalUrl(tokenPocketDappUrl(next.bindUrl), options, telegramWebApp());
1611
+ openExternalUrl(tokenPocketDappUrl(next.bindUrl, options.tokenPocketSource), options, telegramWebApp());
1454
1612
  }
1455
1613
  return next;
1456
1614
  },
@@ -0,0 +1,35 @@
1
+ export interface EosConnectTelegramWebApp {
2
+ initData?: string;
3
+ initDataUnsafe?: {
4
+ user?: {
5
+ language_code?: string;
6
+ };
7
+ };
8
+ platform?: string;
9
+ version?: string;
10
+ openLink?(url: string): void;
11
+ SecureStorage?: {
12
+ setItem(key: string, value: string, callback?: (error: string | null, isStored?: boolean) => void): void;
13
+ getItem(key: string, callback: (error: string | null, value?: string | null) => void): void;
14
+ removeItem?(key: string, callback?: (error: string | null, isRemoved?: boolean) => void): void;
15
+ };
16
+ BiometricManager?: {
17
+ isInited?: boolean;
18
+ isBiometricAvailable?: boolean;
19
+ isAccessRequested?: boolean;
20
+ isAccessGranted?: boolean;
21
+ isBiometricTokenSaved?: boolean;
22
+ init(callback?: () => void): void;
23
+ requestAccess?(params: {
24
+ reason?: string;
25
+ }, callback?: (isGranted: boolean) => void): void;
26
+ authenticate(params: {
27
+ reason?: string;
28
+ }, callback: (isAuthenticated: boolean, biometricToken?: string) => void): void;
29
+ updateBiometricToken(token: string, callback?: (isUpdated: boolean) => void): void;
30
+ openSettings?(): void;
31
+ };
32
+ }
33
+ export declare const EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL = "https://telegram.org/js/telegram-web-app.js";
34
+ export declare function getEosConnectTelegramWebApp(): EosConnectTelegramWebApp | null;
35
+ export declare function loadEosConnectTelegramWebAppSdk(doc?: Document | null | undefined): Promise<void>;
@@ -0,0 +1,37 @@
1
+ export const EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL = 'https://telegram.org/js/telegram-web-app.js';
2
+ let telegramWebAppSdkLoadPromise = null;
3
+ export function getEosConnectTelegramWebApp() {
4
+ const maybeGlobal = globalThis;
5
+ return maybeGlobal.window?.Telegram?.WebApp ?? maybeGlobal.Telegram?.WebApp ?? null;
6
+ }
7
+ export function loadEosConnectTelegramWebAppSdk(doc = globalThis.document) {
8
+ if (getEosConnectTelegramWebApp()) {
9
+ return Promise.resolve();
10
+ }
11
+ if (telegramWebAppSdkLoadPromise) {
12
+ return telegramWebAppSdkLoadPromise;
13
+ }
14
+ if (!doc?.head) {
15
+ return Promise.reject(new Error('Document is not available to load Telegram WebApp SDK'));
16
+ }
17
+ telegramWebAppSdkLoadPromise = new Promise((resolve, reject) => {
18
+ const finish = () => resolve();
19
+ const fail = () => {
20
+ telegramWebAppSdkLoadPromise = null;
21
+ reject(new Error('Failed to load Telegram WebApp SDK'));
22
+ };
23
+ const existing = doc.querySelector(`script[src="${EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL}"]`);
24
+ if (existing) {
25
+ existing.addEventListener('load', finish, { once: true });
26
+ existing.addEventListener('error', fail, { once: true });
27
+ return;
28
+ }
29
+ const script = doc.createElement('script');
30
+ script.src = EOS_CONNECT_TELEGRAM_WEB_APP_SDK_URL;
31
+ script.async = true;
32
+ script.onload = finish;
33
+ script.onerror = fail;
34
+ doc.head.appendChild(script);
35
+ });
36
+ return telegramWebAppSdkLoadPromise;
37
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eos3/connect",
3
- "version": "0.1.4",
3
+ "version": "0.1.9",
4
4
  "description": "Framework-neutral browser SDK for EOS Passkey Connect in Telegram Mini Apps.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",