@2bad/walt 1.0.1 → 2.1.0
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 +18 -1
- package/build/api/balances.d.ts +23 -0
- package/build/api/balances.js +1 -0
- package/build/api/candles.d.ts +22 -0
- package/build/api/candles.js +1 -0
- package/build/api/perps.d.ts +155 -0
- package/build/api/perps.js +1 -0
- package/build/bin/quiet-localstorage.js +8 -0
- package/build/bin/walt.d.ts +1 -0
- package/build/bin/walt.js +152 -0
- package/build/cli/run.d.ts +22 -0
- package/build/cli/run.js +510 -0
- package/build/client.d.ts +38 -0
- package/build/client.js +55 -0
- package/build/core.d.ts +13 -0
- package/build/core.js +5 -0
- package/build/index.d.ts +6 -2
- package/build/index.js +4 -3
- package/build/refresh.d.ts +60 -0
- package/build/refresh.js +130 -0
- package/build/telegram/auth.d.ts +6 -35
- package/build/telegram/auth.js +18 -118
- package/build/telegram/file-storage.d.ts +1 -1
- package/build/transport.js +10 -5
- package/package.json +16 -10
- package/build/bin/login.js +0 -30
- /package/build/bin/{login.d.ts → quiet-localstorage.d.ts} +0 -0
package/build/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { WaltAuthError, WaltRequestError } from "./transport.js";
|
|
2
2
|
import { WaltClient } from "./client.js";
|
|
3
|
+
import { normalizeTransaction } from "./api/transactions.js";
|
|
4
|
+
import { RefreshTokenSource } from "./refresh.js";
|
|
3
5
|
import { TelegramTokenSource } from "./telegram/auth.js";
|
|
4
|
-
|
|
5
|
-
export { TelegramTokenSource, WaltAuthError, WaltClient, WaltRequestError, normalizeTransaction };
|
|
6
|
+
export { RefreshTokenSource, TelegramTokenSource, WaltAuthError, WaltClient, WaltRequestError, normalizeTransaction };
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type TokenState = {
|
|
2
|
+
jwt: string;
|
|
3
|
+
refreshToken: string;
|
|
4
|
+
sessionId: string;
|
|
5
|
+
expiresAt: number;
|
|
6
|
+
deviceSerial: string;
|
|
7
|
+
};
|
|
8
|
+
export type TokenStorage = {
|
|
9
|
+
load: () => Promise<TokenState | null>;
|
|
10
|
+
save: (state: TokenState) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
export type RefreshTokenSourceOptions = {
|
|
13
|
+
/** Where to start. Without it the state comes from `storage.load()`. */
|
|
14
|
+
state?: TokenState;
|
|
15
|
+
/** `save` receives the new state after every renewal, rotated refresh token included. */
|
|
16
|
+
storage?: TokenStorage | undefined;
|
|
17
|
+
/** Origin of the web app, sent as `origin` and `referer`. */
|
|
18
|
+
baseUrl?: string | undefined;
|
|
19
|
+
/** Where the refresh requests go. */
|
|
20
|
+
authUrl?: string | undefined;
|
|
21
|
+
fetch?: typeof fetch | undefined;
|
|
22
|
+
};
|
|
23
|
+
export type AuthResponse = {
|
|
24
|
+
jwt: string;
|
|
25
|
+
refresh_token: string;
|
|
26
|
+
session_id: string;
|
|
27
|
+
access_token_expires_at: string;
|
|
28
|
+
access_token_expires_in: number;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Renews the access token from a stored refresh token over HTTPS, without Telegram.
|
|
32
|
+
* Throws `WaltAuthError` once the refresh token is rejected; a new token state is then needed.
|
|
33
|
+
*/
|
|
34
|
+
export declare class RefreshTokenSource {
|
|
35
|
+
protected readonly baseUrl: string;
|
|
36
|
+
protected readonly storage: TokenStorage | undefined;
|
|
37
|
+
protected state: TokenState | null;
|
|
38
|
+
private readonly authUrl;
|
|
39
|
+
private readonly fetchImpl;
|
|
40
|
+
private restored;
|
|
41
|
+
private pending;
|
|
42
|
+
constructor(options: RefreshTokenSourceOptions);
|
|
43
|
+
/**
|
|
44
|
+
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
45
|
+
*/
|
|
46
|
+
token(rejected?: string): Promise<string>;
|
|
47
|
+
private fresh;
|
|
48
|
+
private resolve;
|
|
49
|
+
private restore;
|
|
50
|
+
/**
|
|
51
|
+
* Called when there is no refresh token to use, or the server rejected it.
|
|
52
|
+
*
|
|
53
|
+
* @param cause - The rejection, if there was one.
|
|
54
|
+
* @returns The new jwt.
|
|
55
|
+
*/
|
|
56
|
+
protected signIn(cause?: unknown): Promise<string>;
|
|
57
|
+
private refresh;
|
|
58
|
+
protected post(path: string, body: Record<string, unknown>): Promise<AuthResponse>;
|
|
59
|
+
protected store(data: AuthResponse, deviceSerial: string): Promise<string>;
|
|
60
|
+
}
|
package/build/refresh.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { BROWSER_HEADERS } from "./browser.js";
|
|
2
|
+
import { WaltAuthError } from "./transport.js";
|
|
3
|
+
// source/refresh.ts
|
|
4
|
+
const BASE_URL = "https://walletbot.me";
|
|
5
|
+
const AUTH_URL = "https://alectryon.walletbot.me";
|
|
6
|
+
const EXPIRY_SKEW_MS = 3e4;
|
|
7
|
+
var AuthEndpointError = class extends Error {
|
|
8
|
+
status;
|
|
9
|
+
constructor(message, status) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.status = status;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
const isRejection = (error) => error instanceof AuthEndpointError && error.status >= 400 && error.status < 500 && error.status !== 429;
|
|
15
|
+
/**
|
|
16
|
+
* Renews the access token from a stored refresh token over HTTPS, without Telegram.
|
|
17
|
+
* Throws `WaltAuthError` once the refresh token is rejected; a new token state is then needed.
|
|
18
|
+
*/
|
|
19
|
+
var RefreshTokenSource = class {
|
|
20
|
+
baseUrl;
|
|
21
|
+
storage;
|
|
22
|
+
state;
|
|
23
|
+
authUrl;
|
|
24
|
+
fetchImpl;
|
|
25
|
+
restored;
|
|
26
|
+
pending = null;
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.state = options.state ?? null;
|
|
29
|
+
this.storage = options.storage;
|
|
30
|
+
this.restored = options.state !== void 0;
|
|
31
|
+
this.baseUrl = options.baseUrl ?? BASE_URL;
|
|
32
|
+
this.authUrl = options.authUrl ?? AUTH_URL;
|
|
33
|
+
this.fetchImpl = options.fetch ?? fetch;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
37
|
+
*/
|
|
38
|
+
async token(rejected) {
|
|
39
|
+
if (this.state && rejected !== void 0 && rejected === this.state.jwt) this.state = {
|
|
40
|
+
...this.state,
|
|
41
|
+
expiresAt: 0
|
|
42
|
+
};
|
|
43
|
+
if (this.state && this.fresh(this.state)) return this.state.jwt;
|
|
44
|
+
this.pending ??= this.resolve();
|
|
45
|
+
try {
|
|
46
|
+
return await this.pending;
|
|
47
|
+
} finally {
|
|
48
|
+
this.pending = null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
fresh(state) {
|
|
52
|
+
return Date.now() < state.expiresAt - EXPIRY_SKEW_MS;
|
|
53
|
+
}
|
|
54
|
+
async resolve() {
|
|
55
|
+
await this.restore();
|
|
56
|
+
const state = this.state;
|
|
57
|
+
if (state && this.fresh(state)) return state.jwt;
|
|
58
|
+
if (state?.refreshToken) try {
|
|
59
|
+
return await this.refresh(state);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (!isRejection(error)) throw error;
|
|
62
|
+
this.state = {
|
|
63
|
+
...state,
|
|
64
|
+
refreshToken: ""
|
|
65
|
+
};
|
|
66
|
+
return this.signIn(error);
|
|
67
|
+
}
|
|
68
|
+
return this.signIn();
|
|
69
|
+
}
|
|
70
|
+
async restore() {
|
|
71
|
+
if (this.restored || !this.storage) return;
|
|
72
|
+
this.restored = true;
|
|
73
|
+
this.state = await this.storage.load();
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Called when there is no refresh token to use, or the server rejected it.
|
|
77
|
+
*
|
|
78
|
+
* @param cause - The rejection, if there was one.
|
|
79
|
+
* @returns The new jwt.
|
|
80
|
+
*/
|
|
81
|
+
signIn(cause) {
|
|
82
|
+
const message = this.state ? "refresh token rejected, a new token state is needed" : "no token state to refresh";
|
|
83
|
+
return Promise.reject(new WaltAuthError(message, { cause }));
|
|
84
|
+
}
|
|
85
|
+
async refresh(state) {
|
|
86
|
+
const data = await this.post("/alectryon/public-api/auth-refresh", {
|
|
87
|
+
session_id: state.sessionId,
|
|
88
|
+
refresh_token: state.refreshToken,
|
|
89
|
+
device_serial: state.deviceSerial
|
|
90
|
+
});
|
|
91
|
+
return this.store(data, state.deviceSerial);
|
|
92
|
+
}
|
|
93
|
+
async post(path, body) {
|
|
94
|
+
const response = await this.fetchImpl(`${this.authUrl}${path}`, {
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
...BROWSER_HEADERS,
|
|
98
|
+
accept: "*/*",
|
|
99
|
+
"sec-fetch-site": new URL(this.authUrl).origin === new URL(this.baseUrl).origin ? "same-origin" : "same-site",
|
|
100
|
+
"content-type": "application/json",
|
|
101
|
+
origin: this.baseUrl,
|
|
102
|
+
referer: `${this.baseUrl}/`,
|
|
103
|
+
"x-wallet-tz-name": Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
104
|
+
"x-wallet-tz-offset": String(-(/* @__PURE__ */ new Date()).getTimezoneOffset())
|
|
105
|
+
},
|
|
106
|
+
body: JSON.stringify(body)
|
|
107
|
+
});
|
|
108
|
+
if (!response.ok) throw new AuthEndpointError(`${path} failed (${response.status}): ${await response.text()}`, response.status);
|
|
109
|
+
return await response.json();
|
|
110
|
+
}
|
|
111
|
+
async store(data, deviceSerial) {
|
|
112
|
+
let expiresAt;
|
|
113
|
+
if (Number.isFinite(data.access_token_expires_in)) expiresAt = Date.now() + data.access_token_expires_in * 1e3;
|
|
114
|
+
else {
|
|
115
|
+
const parsed = Date.parse(data.access_token_expires_at);
|
|
116
|
+
expiresAt = Number.isNaN(parsed) ? Date.now() + 3e5 : parsed;
|
|
117
|
+
}
|
|
118
|
+
const state = {
|
|
119
|
+
jwt: data.jwt,
|
|
120
|
+
refreshToken: data.refresh_token,
|
|
121
|
+
sessionId: data.session_id,
|
|
122
|
+
expiresAt,
|
|
123
|
+
deviceSerial
|
|
124
|
+
};
|
|
125
|
+
this.state = state;
|
|
126
|
+
await this.storage?.save(state);
|
|
127
|
+
return state.jwt;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
export { RefreshTokenSource };
|
package/build/telegram/auth.d.ts
CHANGED
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
jwt: string;
|
|
3
|
-
refreshToken: string;
|
|
4
|
-
sessionId: string;
|
|
5
|
-
expiresAt: number;
|
|
6
|
-
deviceSerial: string;
|
|
7
|
-
};
|
|
8
|
-
export type TokenStorage = {
|
|
9
|
-
load: () => Promise<TokenState | null>;
|
|
10
|
-
save: (state: TokenState) => Promise<void>;
|
|
11
|
-
};
|
|
1
|
+
import { RefreshTokenSource, TokenStorage } from "../refresh.js";
|
|
12
2
|
export type TelegramTokenSourceOptions = {
|
|
13
3
|
session: string;
|
|
14
4
|
apiId: number;
|
|
@@ -24,34 +14,15 @@ export type TelegramTokenSourceOptions = {
|
|
|
24
14
|
storage?: TokenStorage | false;
|
|
25
15
|
maxFloodWaitMs?: number;
|
|
26
16
|
};
|
|
27
|
-
|
|
17
|
+
/** Signs in over Telegram, then renews like `RefreshTokenSource`, signing in again when the refresh token is rejected. */
|
|
18
|
+
export declare class TelegramTokenSource extends RefreshTokenSource {
|
|
28
19
|
private readonly client;
|
|
29
20
|
private readonly bot;
|
|
30
|
-
private readonly baseUrl;
|
|
31
|
-
private readonly authUrl;
|
|
32
|
-
private readonly fetchImpl;
|
|
33
|
-
private readonly storage;
|
|
34
21
|
private readonly maxFloodWaitMs;
|
|
35
|
-
private
|
|
36
|
-
private
|
|
37
|
-
private jwt;
|
|
38
|
-
private refreshToken;
|
|
39
|
-
private sessionId;
|
|
40
|
-
private expiresAt;
|
|
41
|
-
private restored;
|
|
42
|
-
private pending;
|
|
22
|
+
private readonly knownDeviceSerial;
|
|
23
|
+
private readonly newDeviceSerial;
|
|
43
24
|
constructor(options: TelegramTokenSourceOptions);
|
|
44
|
-
|
|
45
|
-
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
46
|
-
*/
|
|
47
|
-
token(rejected?: string): Promise<string>;
|
|
48
|
-
private fresh;
|
|
49
|
-
private resolve;
|
|
50
|
-
private restore;
|
|
51
|
-
private authenticate;
|
|
25
|
+
protected signIn(): Promise<string>;
|
|
52
26
|
private requestInitData;
|
|
53
|
-
private refresh;
|
|
54
|
-
private post;
|
|
55
|
-
private store;
|
|
56
27
|
disconnect(): Promise<void>;
|
|
57
28
|
}
|
package/build/telegram/auth.js
CHANGED
|
@@ -1,97 +1,40 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { RefreshTokenSource } from "../refresh.js";
|
|
2
2
|
import { defaultTokenPath, fileTokenStorage } from "./file-storage.js";
|
|
3
3
|
import { Api, TelegramClient, errors } from "teleproto";
|
|
4
4
|
import { StringSession } from "teleproto/sessions/index.js";
|
|
5
5
|
// source/telegram/auth.ts
|
|
6
|
-
const BASE_URL = "https://walletbot.me";
|
|
7
|
-
const AUTH_URL = "https://alectryon.walletbot.me";
|
|
8
|
-
const EXPIRY_SKEW_MS = 3e4;
|
|
9
6
|
const MAX_FLOOD_WAIT_MS = 6e4;
|
|
10
7
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
constructor(message, status) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.status = status;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
var TelegramTokenSource = class {
|
|
8
|
+
/** Signs in over Telegram, then renews like `RefreshTokenSource`, signing in again when the refresh token is rejected. */
|
|
9
|
+
var TelegramTokenSource = class extends RefreshTokenSource {
|
|
19
10
|
client;
|
|
20
11
|
bot;
|
|
21
|
-
baseUrl;
|
|
22
|
-
authUrl;
|
|
23
|
-
fetchImpl;
|
|
24
|
-
storage;
|
|
25
12
|
maxFloodWaitMs;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
jwt = null;
|
|
29
|
-
refreshToken = null;
|
|
30
|
-
sessionId = null;
|
|
31
|
-
expiresAt = 0;
|
|
32
|
-
restored = false;
|
|
33
|
-
pending = null;
|
|
13
|
+
knownDeviceSerial;
|
|
14
|
+
newDeviceSerial = `weba:${crypto.randomUUID()}`;
|
|
34
15
|
constructor(options) {
|
|
16
|
+
super({
|
|
17
|
+
storage: options.storage === false ? void 0 : options.storage ?? fileTokenStorage(defaultTokenPath(options.session)),
|
|
18
|
+
baseUrl: options.baseUrl,
|
|
19
|
+
authUrl: options.authUrl,
|
|
20
|
+
fetch: options.fetch
|
|
21
|
+
});
|
|
35
22
|
this.client = new TelegramClient(new StringSession(options.session), options.apiId, options.apiHash, { connectionRetries: 3 });
|
|
36
23
|
this.bot = options.bot ?? "walt";
|
|
37
|
-
this.
|
|
38
|
-
this.deviceSerialKnown = options.deviceSerial !== void 0;
|
|
39
|
-
this.baseUrl = options.baseUrl ?? BASE_URL;
|
|
40
|
-
this.authUrl = options.authUrl ?? AUTH_URL;
|
|
41
|
-
this.fetchImpl = options.fetch ?? fetch;
|
|
42
|
-
this.storage = options.storage === false ? void 0 : options.storage ?? fileTokenStorage(defaultTokenPath(options.session));
|
|
24
|
+
this.knownDeviceSerial = options.deviceSerial;
|
|
43
25
|
this.maxFloodWaitMs = options.maxFloodWaitMs ?? MAX_FLOOD_WAIT_MS;
|
|
44
26
|
}
|
|
45
|
-
|
|
46
|
-
* @param rejected - A token the API answered with 401. If it is the cached one, it is renewed even before expiry.
|
|
47
|
-
*/
|
|
48
|
-
async token(rejected) {
|
|
49
|
-
if (rejected !== void 0 && rejected === this.jwt) this.expiresAt = 0;
|
|
50
|
-
if (this.fresh()) return this.jwt;
|
|
51
|
-
this.pending ??= this.resolve();
|
|
52
|
-
try {
|
|
53
|
-
return await this.pending;
|
|
54
|
-
} finally {
|
|
55
|
-
this.pending = null;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
fresh() {
|
|
59
|
-
return this.jwt !== null && Date.now() < this.expiresAt - EXPIRY_SKEW_MS;
|
|
60
|
-
}
|
|
61
|
-
async resolve() {
|
|
62
|
-
await this.restore();
|
|
63
|
-
if (this.fresh()) return this.jwt;
|
|
64
|
-
if (this.refreshToken) try {
|
|
65
|
-
return await this.refresh();
|
|
66
|
-
} catch (error) {
|
|
67
|
-
if (!(error instanceof AuthEndpointError && error.status >= 400 && error.status < 500 && error.status !== 429)) throw error;
|
|
68
|
-
this.refreshToken = null;
|
|
69
|
-
}
|
|
70
|
-
return this.authenticate();
|
|
71
|
-
}
|
|
72
|
-
async restore() {
|
|
73
|
-
if (this.restored || !this.storage) return;
|
|
74
|
-
this.restored = true;
|
|
75
|
-
const state = await this.storage.load();
|
|
76
|
-
if (!state) return;
|
|
77
|
-
this.jwt = state.jwt;
|
|
78
|
-
this.refreshToken = state.refreshToken;
|
|
79
|
-
this.sessionId = state.sessionId;
|
|
80
|
-
this.expiresAt = state.expiresAt;
|
|
81
|
-
this.deviceSerial = state.deviceSerial;
|
|
82
|
-
this.deviceSerialKnown = true;
|
|
83
|
-
}
|
|
84
|
-
async authenticate() {
|
|
27
|
+
async signIn() {
|
|
85
28
|
const initData = await this.requestInitData();
|
|
86
|
-
const
|
|
87
|
-
const
|
|
29
|
+
const known = this.state?.deviceSerial ?? this.knownDeviceSerial;
|
|
30
|
+
const serial = known === void 0 ? { device_serial_fallback: this.newDeviceSerial } : { device_serial: known };
|
|
31
|
+
const data = await this.post("/alectryon/public-api/auth", {
|
|
88
32
|
web_view_init_data_raw: initData,
|
|
89
33
|
ep: "menu",
|
|
90
34
|
device_serial_multi_domain_backup_supported: true,
|
|
91
35
|
...serial
|
|
92
|
-
})
|
|
93
|
-
this.
|
|
94
|
-
return token;
|
|
36
|
+
});
|
|
37
|
+
return this.store(data, known ?? this.newDeviceSerial);
|
|
95
38
|
}
|
|
96
39
|
async requestInitData(retryAfterFlood = true) {
|
|
97
40
|
if (!this.client.connected) await this.client.connect();
|
|
@@ -114,49 +57,6 @@ var TelegramTokenSource = class {
|
|
|
114
57
|
if (!initData) throw new Error("no tgWebAppData in web view url");
|
|
115
58
|
return initData;
|
|
116
59
|
}
|
|
117
|
-
async refresh() {
|
|
118
|
-
return this.store(await this.post("/alectryon/public-api/auth-refresh", {
|
|
119
|
-
session_id: this.sessionId,
|
|
120
|
-
refresh_token: this.refreshToken,
|
|
121
|
-
device_serial: this.deviceSerial
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
async post(path, body) {
|
|
125
|
-
const response = await this.fetchImpl(`${this.authUrl}${path}`, {
|
|
126
|
-
method: "POST",
|
|
127
|
-
headers: {
|
|
128
|
-
...BROWSER_HEADERS,
|
|
129
|
-
accept: "*/*",
|
|
130
|
-
"sec-fetch-site": new URL(this.authUrl).origin === new URL(this.baseUrl).origin ? "same-origin" : "same-site",
|
|
131
|
-
"content-type": "application/json",
|
|
132
|
-
origin: this.baseUrl,
|
|
133
|
-
referer: `${this.baseUrl}/`,
|
|
134
|
-
"x-wallet-tz-name": Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
135
|
-
"x-wallet-tz-offset": String(-(/* @__PURE__ */ new Date()).getTimezoneOffset())
|
|
136
|
-
},
|
|
137
|
-
body: JSON.stringify(body)
|
|
138
|
-
});
|
|
139
|
-
if (!response.ok) throw new AuthEndpointError(`${path} failed (${response.status}): ${await response.text()}`, response.status);
|
|
140
|
-
return await response.json();
|
|
141
|
-
}
|
|
142
|
-
async store(data) {
|
|
143
|
-
this.jwt = data.jwt;
|
|
144
|
-
this.refreshToken = data.refresh_token;
|
|
145
|
-
this.sessionId = data.session_id;
|
|
146
|
-
if (Number.isFinite(data.access_token_expires_in)) this.expiresAt = Date.now() + data.access_token_expires_in * 1e3;
|
|
147
|
-
else {
|
|
148
|
-
const parsed = Date.parse(data.access_token_expires_at);
|
|
149
|
-
this.expiresAt = Number.isNaN(parsed) ? Date.now() + 3e5 : parsed;
|
|
150
|
-
}
|
|
151
|
-
await this.storage?.save({
|
|
152
|
-
jwt: this.jwt,
|
|
153
|
-
refreshToken: this.refreshToken,
|
|
154
|
-
sessionId: this.sessionId,
|
|
155
|
-
expiresAt: this.expiresAt,
|
|
156
|
-
deviceSerial: this.deviceSerial
|
|
157
|
-
});
|
|
158
|
-
return this.jwt;
|
|
159
|
-
}
|
|
160
60
|
async disconnect() {
|
|
161
61
|
await this.client.disconnect();
|
|
162
62
|
}
|
package/build/transport.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { BROWSER_HEADERS, SENTRY_PUBLIC_KEY, SENTRY_RELEASE } from "./browser.js";
|
|
2
|
-
import { randomBytes } from "node:crypto";
|
|
3
2
|
// source/transport.ts
|
|
4
3
|
var WaltAuthError = class extends Error {};
|
|
5
4
|
var WaltRequestError = class extends Error {
|
|
@@ -23,8 +22,13 @@ const RETRYABLE_STATUS = /* @__PURE__ */ new Set([
|
|
|
23
22
|
503,
|
|
24
23
|
504
|
|
25
24
|
]);
|
|
25
|
+
const BEARER_SERVICES = [
|
|
26
|
+
"/emporos/",
|
|
27
|
+
"/portmone/",
|
|
28
|
+
"/kandelabr/"
|
|
29
|
+
];
|
|
26
30
|
const MAX_RETRY_DELAY_MS = 3e4;
|
|
27
|
-
const hex = (bytes) =>
|
|
31
|
+
const hex = (bytes) => Array.from(crypto.getRandomValues(new Uint8Array(bytes)), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
28
32
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
33
|
const parseBody = (text) => {
|
|
30
34
|
try {
|
|
@@ -39,8 +43,9 @@ const retryAfterOf = (response) => {
|
|
|
39
43
|
};
|
|
40
44
|
const telegramUserIdOf = (token) => {
|
|
41
45
|
try {
|
|
42
|
-
const payload = token.split(".")[1] ?? "";
|
|
43
|
-
const
|
|
46
|
+
const payload = (token.split(".")[1] ?? "").replaceAll("-", "+").replaceAll("_", "/");
|
|
47
|
+
const bytes = Uint8Array.from(atob(payload), (char) => char.charCodeAt(0));
|
|
48
|
+
const id = JSON.parse(new TextDecoder().decode(bytes)).web_view_init_data?.user?.id;
|
|
44
49
|
return typeof id === "number" ? id : null;
|
|
45
50
|
} catch {
|
|
46
51
|
return null;
|
|
@@ -121,7 +126,7 @@ var Transport = class {
|
|
|
121
126
|
const headers = {
|
|
122
127
|
...BROWSER_HEADERS,
|
|
123
128
|
accept: "application/json",
|
|
124
|
-
authorization: path.startsWith(
|
|
129
|
+
authorization: BEARER_SERVICES.some((prefix) => path.startsWith(prefix)) ? `Bearer ${jwt}` : jwt,
|
|
125
130
|
referer: `${this.baseUrl}/main?ep=menu${userId === null ? "" : `&tgWebAppUserId=${userId}`}`,
|
|
126
131
|
baggage: this.baggage,
|
|
127
132
|
"sentry-trace": `${this.traceId}-${hex(8)}-0`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2bad/walt",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Client for the Walt (ex. Wallet) API in Telegram to track a crypto portfolio and exchange between assets",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"telegram",
|
|
@@ -33,37 +33,43 @@
|
|
|
33
33
|
"types": "./build/index.d.ts",
|
|
34
34
|
"default": "./build/index.js"
|
|
35
35
|
}
|
|
36
|
+
},
|
|
37
|
+
"./client": {
|
|
38
|
+
"import": {
|
|
39
|
+
"types": "./build/core.d.ts",
|
|
40
|
+
"default": "./build/core.js"
|
|
41
|
+
}
|
|
36
42
|
}
|
|
37
43
|
},
|
|
38
44
|
"bin": {
|
|
39
|
-
"
|
|
45
|
+
"walt": "./build/bin/walt.js"
|
|
40
46
|
},
|
|
41
47
|
"files": [
|
|
42
48
|
"build"
|
|
43
49
|
],
|
|
44
50
|
"dependencies": {
|
|
45
|
-
"teleproto": "1.232.
|
|
51
|
+
"teleproto": "1.232.1"
|
|
46
52
|
},
|
|
47
53
|
"devDependencies": {
|
|
48
54
|
"@2bad/axiom": "2.1.0",
|
|
49
55
|
"@2bad/tsconfig": "4.0.0",
|
|
50
56
|
"@arethetypeswrong/cli": "0.18.5",
|
|
51
57
|
"@types/node": "26.6.6",
|
|
52
|
-
"@vitest/coverage-v8": "5.0
|
|
58
|
+
"@vitest/coverage-v8": "5.1.0",
|
|
53
59
|
"npm-run-all2": "9.0.4",
|
|
54
|
-
"oxfmt": "0.
|
|
55
|
-
"oxlint": "1.
|
|
60
|
+
"oxfmt": "0.76.0",
|
|
61
|
+
"oxlint": "1.91.0",
|
|
56
62
|
"publint": "0.3.25",
|
|
57
|
-
"tsdown": "0.23.
|
|
63
|
+
"tsdown": "0.23.5",
|
|
58
64
|
"typescript": "7.0.2",
|
|
59
|
-
"vitest": "5.0
|
|
65
|
+
"vitest": "5.1.0"
|
|
60
66
|
},
|
|
61
67
|
"engines": {
|
|
62
68
|
"node": ">=26"
|
|
63
69
|
},
|
|
64
70
|
"volta": {
|
|
65
71
|
"node": "26.13.0",
|
|
66
|
-
"pnpm": "12.
|
|
72
|
+
"pnpm": "12.18.0"
|
|
67
73
|
},
|
|
68
74
|
"scripts": {
|
|
69
75
|
"build": "tsdown",
|
|
@@ -74,7 +80,7 @@
|
|
|
74
80
|
"fix": "run-p fix:**",
|
|
75
81
|
"fix:code": "oxlint --fix ./source",
|
|
76
82
|
"fix:format": "oxfmt ./source ./examples",
|
|
77
|
-
"telegram:login": "node --env-file-if-exists=.env
|
|
83
|
+
"telegram:login": "run-s build && node --env-file-if-exists=.env build/bin/walt.js login",
|
|
78
84
|
"example": "run-s build && node --env-file-if-exists=.env examples/portfolio.ts",
|
|
79
85
|
"preversion": "run-s check test build",
|
|
80
86
|
"test": "vitest run",
|
package/build/bin/login.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { TelegramClient } from "teleproto";
|
|
3
|
-
import { StringSession } from "teleproto/sessions/index.js";
|
|
4
|
-
import { createInterface } from "node:readline/promises";
|
|
5
|
-
import { stdin, stdout } from "node:process";
|
|
6
|
-
// source/bin/login.ts
|
|
7
|
-
const rl = createInterface({
|
|
8
|
-
input: stdin,
|
|
9
|
-
output: stdout
|
|
10
|
-
});
|
|
11
|
-
const ask = (question) => rl.question(question);
|
|
12
|
-
const apiId = Number(process.env["TG_API_ID"] || await ask("API id (from https://my.telegram.org): "));
|
|
13
|
-
if (!Number.isSafeInteger(apiId) || apiId <= 0) {
|
|
14
|
-
console.error("The API id must be the number shown at https://my.telegram.org");
|
|
15
|
-
process.exit(1);
|
|
16
|
-
}
|
|
17
|
-
const apiHash = process.env["TG_API_HASH"] || await ask("API hash: ");
|
|
18
|
-
const client = new TelegramClient(new StringSession(""), apiId, apiHash, { connectionRetries: 3 });
|
|
19
|
-
await client.start({
|
|
20
|
-
phoneNumber: () => ask("Phone (+country...): "),
|
|
21
|
-
phoneCode: () => ask("Code you received: "),
|
|
22
|
-
password: () => ask("2FA password (blank if none): "),
|
|
23
|
-
onError: (error) => console.error(error)
|
|
24
|
-
});
|
|
25
|
-
rl.close();
|
|
26
|
-
console.log("\nLogged in. Save this as TG_SESSION (keep it secret):\n");
|
|
27
|
-
console.log(client.session.save());
|
|
28
|
-
await client.disconnect();
|
|
29
|
-
process.exit(0);
|
|
30
|
-
export {};
|
|
File without changes
|