macbid-ts-api 1.0.0-beta.4 → 1.1.0-beta.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/MacBid.d.ts +118 -12
- package/MacBid.d.ts.map +1 -1
- package/MacBid.js +195 -134
- package/MacBid.js.map +1 -1
- package/out/active.json +212 -0
- package/out/buildings +842 -0
- package/out/locations.json +1277 -0
- package/package.json +8 -27
- package/readme.md +86 -1
- package/MacBid.ts +0 -423
- package/index.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "macbid-ts-api",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0-beta.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/pkmnct/macbid-ts-api.git"
|
|
@@ -17,35 +17,16 @@
|
|
|
17
17
|
"node": ">=16.0.0"
|
|
18
18
|
},
|
|
19
19
|
"devDependencies": {
|
|
20
|
-
"@
|
|
21
|
-
"@
|
|
22
|
-
"
|
|
23
|
-
"eslint": "^
|
|
24
|
-
"
|
|
25
|
-
"eslint
|
|
26
|
-
"typescript": "^5.2.2"
|
|
27
|
-
},
|
|
28
|
-
"eslintConfig": {
|
|
29
|
-
"parser": "@typescript-eslint/parser",
|
|
30
|
-
"parserOptions": {
|
|
31
|
-
"ecmaVersion": 2021,
|
|
32
|
-
"sourceType": "module"
|
|
33
|
-
},
|
|
34
|
-
"extends": [
|
|
35
|
-
"eslint:recommended",
|
|
36
|
-
"plugin:@typescript-eslint/recommended",
|
|
37
|
-
"plugin:promise/recommended",
|
|
38
|
-
"plugin:import/recommended",
|
|
39
|
-
"plugin:import/typescript"
|
|
40
|
-
],
|
|
41
|
-
"plugins": [
|
|
42
|
-
"@typescript-eslint",
|
|
43
|
-
"promise",
|
|
44
|
-
"import"
|
|
45
|
-
]
|
|
20
|
+
"@eslint/js": "^10.0.1",
|
|
21
|
+
"@types/node-fetch": "^2.6.13",
|
|
22
|
+
"eslint": "^10.5.0",
|
|
23
|
+
"eslint-plugin-promise": "^7.3.0",
|
|
24
|
+
"typescript": "^6.0.3",
|
|
25
|
+
"typescript-eslint": "^8.61.1"
|
|
46
26
|
},
|
|
47
27
|
"scripts": {
|
|
48
28
|
"build": "tsc",
|
|
29
|
+
"lint": "eslint .",
|
|
49
30
|
"prepublish": "npm run build"
|
|
50
31
|
},
|
|
51
32
|
"type": "module",
|
package/readme.md
CHANGED
|
@@ -1 +1,86 @@
|
|
|
1
|
-
#
|
|
1
|
+
# macbid-ts-api
|
|
2
|
+
|
|
3
|
+
Unofficial TypeScript client for the [Mac.Bid](https://www.mac.bid) API.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
Node.js **>= 16**
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install macbid-ts-api
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Usage
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import MacBid, { type SerializableAuthState } from "macbid-ts-api";
|
|
19
|
+
|
|
20
|
+
const api = new MacBid();
|
|
21
|
+
let state: SerializableAuthState = {};
|
|
22
|
+
|
|
23
|
+
state = await api.authenticate({
|
|
24
|
+
email: "you@example.com",
|
|
25
|
+
password: "your-password",
|
|
26
|
+
...state,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const watchlist = await api.get_watchlist();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`authenticate()` returns persistable state (tokens and `device_id`, never credentials). Pass it back in on the next call with `...state`.
|
|
33
|
+
|
|
34
|
+
### Two-factor authentication
|
|
35
|
+
|
|
36
|
+
If SMS verification is required, the first call throws after sending a code. Set `state` from `api.getAuthState()` and call again with the code:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
let state: SerializableAuthState = {};
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
state = await api.authenticate({ email, password, ...state });
|
|
43
|
+
} catch {
|
|
44
|
+
state = api.getAuthState();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// User receives SMS, then retry:
|
|
48
|
+
state = await api.authenticate({
|
|
49
|
+
email,
|
|
50
|
+
password,
|
|
51
|
+
validation_code: "123456",
|
|
52
|
+
...state,
|
|
53
|
+
});
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
If `device_id` is set but there are no tokens and no `validation_code`, a code was already sent and a new SMS will not be requested.
|
|
57
|
+
|
|
58
|
+
For JSON storage, use `MacBid.serializeAuthState` / `MacBid.parseAuthState`.
|
|
59
|
+
|
|
60
|
+
`AuthInfo` is the type for `authenticate()` params (credentials, `validation_code`, etc.). `SerializableAuthState` is what comes back — safe to persist.
|
|
61
|
+
|
|
62
|
+
## API
|
|
63
|
+
|
|
64
|
+
| Method | Description |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `authenticate(params?)` | Log in or refresh session; returns persistable state |
|
|
67
|
+
| `get_watchlist()` | Active watchlist items |
|
|
68
|
+
| `get_active()` | Active won items (e.g. awaiting pickup) |
|
|
69
|
+
| `get_buildings()` | Warehouse buildings |
|
|
70
|
+
| `get_locations()` | Pickup locations |
|
|
71
|
+
| `get(path)` / `post(path, options?)` | Authenticated API requests |
|
|
72
|
+
| `refreshToken()` | Refresh the access token |
|
|
73
|
+
| `getAuthState()` | Current persistable state |
|
|
74
|
+
| `get_refresh_token_expiration()` | Refresh token expiry |
|
|
75
|
+
|
|
76
|
+
## Development
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npm install
|
|
80
|
+
npm run build
|
|
81
|
+
npm run lint
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/MacBid.ts
DELETED
|
@@ -1,423 +0,0 @@
|
|
|
1
|
-
import fetch, { Response } from "node-fetch";
|
|
2
|
-
import { promises as fs } from "node:fs";
|
|
3
|
-
|
|
4
|
-
export interface AuthInfo {
|
|
5
|
-
email?: string;
|
|
6
|
-
password?: string;
|
|
7
|
-
token?: string;
|
|
8
|
-
token_expiration?: Date;
|
|
9
|
-
user_id?: string;
|
|
10
|
-
refresh_token?: string;
|
|
11
|
-
refresh_token_expiration?: Date;
|
|
12
|
-
validation_code?: string;
|
|
13
|
-
device_id?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface WatchlistFull {
|
|
17
|
-
auction_lot_id: number;
|
|
18
|
-
watchlist_date_created: Date;
|
|
19
|
-
id: number;
|
|
20
|
-
auction_id: number;
|
|
21
|
-
closed_date: null;
|
|
22
|
-
buyers_assurance_cost: number | null;
|
|
23
|
-
expected_close_date: Date;
|
|
24
|
-
inventory_id: number;
|
|
25
|
-
date_created: Date;
|
|
26
|
-
lot_number: string;
|
|
27
|
-
listing_url: null;
|
|
28
|
-
title: string;
|
|
29
|
-
is_open: number;
|
|
30
|
-
is_transferrable: number;
|
|
31
|
-
total_bids: number;
|
|
32
|
-
winning_customer_id: null;
|
|
33
|
-
winning_bid_id: null;
|
|
34
|
-
winning_bid_amount: number | null;
|
|
35
|
-
unique_bidders: number;
|
|
36
|
-
product_name: string;
|
|
37
|
-
quantity: number;
|
|
38
|
-
is_pallet: number;
|
|
39
|
-
shipping_height: number | null;
|
|
40
|
-
shipping_width: number | null;
|
|
41
|
-
shipping_length: number | null;
|
|
42
|
-
warehouse_location: string;
|
|
43
|
-
shipping_weight: number | null;
|
|
44
|
-
case_packed_qty: number | null;
|
|
45
|
-
auction_number: string;
|
|
46
|
-
retail_price: number;
|
|
47
|
-
condition_name: ConditionName;
|
|
48
|
-
category: null | string;
|
|
49
|
-
image_url: string;
|
|
50
|
-
auction_type: AuctionType;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export enum AuctionType {
|
|
54
|
-
Pallet = "pallet",
|
|
55
|
-
Standard = "standard",
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export enum ConditionName {
|
|
59
|
-
Damaged = "DAMAGED",
|
|
60
|
-
LikeNew = "LIKE NEW",
|
|
61
|
-
OpenBox = "OPEN BOX",
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface MacBidApiResponse extends Response {
|
|
65
|
-
json: () => Promise<{
|
|
66
|
-
[key: string]: unknown;
|
|
67
|
-
}>;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export class MacBid {
|
|
71
|
-
public LOGIN_PAGE_URL = "https://www.mac.bid";
|
|
72
|
-
public API_ROOT = "https://api.macdiscount.com";
|
|
73
|
-
|
|
74
|
-
private macbid_session_headers: { [key: string]: string } = {
|
|
75
|
-
"Content-Type": "application/json",
|
|
76
|
-
};
|
|
77
|
-
private auth_info: AuthInfo;
|
|
78
|
-
private tokenFilePath?: string;
|
|
79
|
-
|
|
80
|
-
constructor(auth_info: AuthInfo, tokenFilePath?: string) {
|
|
81
|
-
this.auth_info = auth_info;
|
|
82
|
-
this.tokenFilePath = tokenFilePath;
|
|
83
|
-
// Don't authenticate in constructor - wait until actually needed
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
public authenticate = async () => {
|
|
87
|
-
if (this.auth_info) {
|
|
88
|
-
// If we have a token, use it (will be auto-refreshed if expired)
|
|
89
|
-
if (this.auth_info.token) {
|
|
90
|
-
this.macbid_session_headers["Authorization"] = this.auth_info
|
|
91
|
-
.token as string;
|
|
92
|
-
console.log("Using existing access token");
|
|
93
|
-
// Token will be auto-refreshed by ensureValidToken if needed
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// If we have a refresh token but no access token, try to refresh first
|
|
98
|
-
if (this.auth_info.refresh_token) {
|
|
99
|
-
if (this.isRefreshTokenExpired()) {
|
|
100
|
-
console.log("Refresh token has expired, need to login");
|
|
101
|
-
} else {
|
|
102
|
-
console.log("No access token, attempting to refresh using refresh token");
|
|
103
|
-
try {
|
|
104
|
-
await this.refreshToken();
|
|
105
|
-
console.log("Successfully refreshed token");
|
|
106
|
-
return;
|
|
107
|
-
} catch (error) {
|
|
108
|
-
// If refresh fails, fall through to login
|
|
109
|
-
console.warn("Failed to refresh token, attempting login:", error);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// No valid tokens, need to login
|
|
115
|
-
if (this.auth_info.email && this.auth_info.password) {
|
|
116
|
-
console.log("No valid tokens found, attempting login");
|
|
117
|
-
await this.login(this.auth_info.email, this.auth_info.password);
|
|
118
|
-
} else {
|
|
119
|
-
throw new Error("Invalid auth_info");
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
public get = async (path: string): Promise<MacBidApiResponse> => {
|
|
125
|
-
await this.ensureValidToken();
|
|
126
|
-
return (await fetch(this.API_ROOT + path, {
|
|
127
|
-
headers: this.macbid_session_headers,
|
|
128
|
-
})) as MacBidApiResponse;
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
public post = async (
|
|
132
|
-
path: string,
|
|
133
|
-
options?: RequestInit
|
|
134
|
-
): Promise<MacBidApiResponse> => {
|
|
135
|
-
// Don't refresh token for auth endpoints
|
|
136
|
-
if (!path.includes("/auth/")) {
|
|
137
|
-
await this.ensureValidToken();
|
|
138
|
-
}
|
|
139
|
-
return (await fetch(this.API_ROOT + path, {
|
|
140
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
141
|
-
// @ts-ignore
|
|
142
|
-
headers: this.macbid_session_headers,
|
|
143
|
-
method: "POST",
|
|
144
|
-
...options,
|
|
145
|
-
})) as MacBidApiResponse;
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Raise an exception if an endpoint requiring login is called without valid auth
|
|
150
|
-
*/
|
|
151
|
-
private check_auth = (): boolean => {
|
|
152
|
-
if (!this.macbid_session_headers["Authorization"]) {
|
|
153
|
-
throw new Error("Not authenticated");
|
|
154
|
-
}
|
|
155
|
-
return true;
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Do the login request
|
|
160
|
-
* @param email - User email
|
|
161
|
-
* @param password - User password
|
|
162
|
-
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
163
|
-
*/
|
|
164
|
-
public login = async (
|
|
165
|
-
email: string,
|
|
166
|
-
password: string,
|
|
167
|
-
validation_code?: string
|
|
168
|
-
): Promise<boolean> => {
|
|
169
|
-
// Use existing device_id if available, otherwise generate a new one
|
|
170
|
-
const device_id = this.auth_info.device_id || crypto.randomUUID();
|
|
171
|
-
if (!this.auth_info.device_id) {
|
|
172
|
-
this.auth_info.device_id = device_id;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const login_params = {
|
|
176
|
-
device_id: device_id,
|
|
177
|
-
email: email,
|
|
178
|
-
password: password,
|
|
179
|
-
ref_code: null,
|
|
180
|
-
ref_r: null,
|
|
181
|
-
remember_me: true,
|
|
182
|
-
utm_campaign: null,
|
|
183
|
-
utm_medium: null,
|
|
184
|
-
utm_source: null,
|
|
185
|
-
};
|
|
186
|
-
// https://api.macdiscount.com/auth/auth-validation
|
|
187
|
-
const res = await this.post("/auth/auth-validation", {
|
|
188
|
-
body: JSON.stringify(login_params),
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
const resJson = await res.json();
|
|
192
|
-
|
|
193
|
-
if (resJson["message"] === "Login validation code sent") {
|
|
194
|
-
// Get validation code from parameter, auth_info, or throw error
|
|
195
|
-
const code =
|
|
196
|
-
validation_code ||
|
|
197
|
-
this.auth_info.validation_code ||
|
|
198
|
-
process.env.MACBID_VALIDATION_CODE;
|
|
199
|
-
|
|
200
|
-
if (!code) {
|
|
201
|
-
throw new Error(
|
|
202
|
-
"Validation code required. Provide it via:\n" +
|
|
203
|
-
" 1. login() method parameter: login(email, password, validation_code)\n" +
|
|
204
|
-
" 2. AuthInfo.validation_code when creating MacBid instance\n" +
|
|
205
|
-
" 3. MACBID_VALIDATION_CODE environment variable"
|
|
206
|
-
);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const validation_params = {
|
|
210
|
-
code: code,
|
|
211
|
-
device_id: login_params.device_id,
|
|
212
|
-
new_password: "",
|
|
213
|
-
remember_me: true,
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const validation_res = await this.post("/auth/validate-access-code", {
|
|
217
|
-
body: JSON.stringify(validation_params),
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
const validation_resJson = await validation_res.json();
|
|
221
|
-
|
|
222
|
-
// Check if validation failed
|
|
223
|
-
if (validation_res.status !== 200 || validation_resJson["error"]) {
|
|
224
|
-
console.error("Validation failed:", JSON.stringify(validation_resJson, null, 2));
|
|
225
|
-
throw new Error(`Validation failed: ${validation_resJson["error"] || validation_resJson["message"] || "Unknown error"}`);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const access_token = validation_resJson["access_token"] as string;
|
|
229
|
-
const refresh_token = validation_resJson["refresh_token"] as string;
|
|
230
|
-
const user_id = validation_resJson["user_id"] as string;
|
|
231
|
-
const expires = validation_resJson["expires"] as number;
|
|
232
|
-
const expiration_refresh = validation_resJson["expiration_refresh"] as number;
|
|
233
|
-
|
|
234
|
-
if (access_token) {
|
|
235
|
-
this.auth_info.token = access_token as string;
|
|
236
|
-
this.auth_info.user_id = user_id as string;
|
|
237
|
-
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
238
|
-
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
239
|
-
this.auth_info.refresh_token = refresh_token;
|
|
240
|
-
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
241
|
-
await this.saveTokens();
|
|
242
|
-
console.log("Login successful, tokens saved");
|
|
243
|
-
return true;
|
|
244
|
-
} else {
|
|
245
|
-
console.error("No access token in validation response:", JSON.stringify(validation_resJson, null, 2));
|
|
246
|
-
throw new Error("Login failed: No access token received");
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
console.error("Unexpected login response:", JSON.stringify(resJson, null, 2));
|
|
250
|
-
throw new Error("Login failed");
|
|
251
|
-
};
|
|
252
|
-
|
|
253
|
-
public get_refresh_token_expiration = (): Date => {
|
|
254
|
-
if (this.auth_info.refresh_token_expiration) {
|
|
255
|
-
return this.auth_info.refresh_token_expiration;
|
|
256
|
-
} else {
|
|
257
|
-
throw new Error("Refresh token expiration not set, make sure to login first.");
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
263
|
-
*/
|
|
264
|
-
private isTokenExpired = (): boolean => {
|
|
265
|
-
if (!this.auth_info.token_expiration) {
|
|
266
|
-
return true;
|
|
267
|
-
}
|
|
268
|
-
// Refresh if token expires within 5 minutes
|
|
269
|
-
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
270
|
-
return Date.now() >= this.auth_info.token_expiration.getTime() - bufferTime;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Check if the refresh token is expired
|
|
275
|
-
*/
|
|
276
|
-
private isRefreshTokenExpired = (): boolean => {
|
|
277
|
-
if (!this.auth_info.refresh_token_expiration) {
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
280
|
-
return Date.now() >= this.auth_info.refresh_token_expiration.getTime();
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* Refresh the access token using the refresh token
|
|
285
|
-
*/
|
|
286
|
-
public refreshToken = async (): Promise<boolean> => {
|
|
287
|
-
if (!this.auth_info.refresh_token) {
|
|
288
|
-
throw new Error("No refresh token available. Please login again.");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (this.isRefreshTokenExpired()) {
|
|
292
|
-
throw new Error("Refresh token has expired. Please login again.");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const refresh_params = {
|
|
296
|
-
refresh_token: this.auth_info.refresh_token,
|
|
297
|
-
};
|
|
298
|
-
|
|
299
|
-
// Use fetch directly to avoid triggering ensureValidToken and use PUT method
|
|
300
|
-
const res = await fetch(this.API_ROOT + "/auth/refresh-token", {
|
|
301
|
-
method: "PUT",
|
|
302
|
-
body: JSON.stringify(refresh_params),
|
|
303
|
-
headers: {
|
|
304
|
-
"Content-Type": "application/json",
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const resJson = (await res.json()) as {
|
|
309
|
-
error?: string;
|
|
310
|
-
access_token?: string;
|
|
311
|
-
refresh_token?: string;
|
|
312
|
-
expires?: number;
|
|
313
|
-
expiration_refresh?: number;
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
// Check for error response
|
|
317
|
-
if (resJson["error"]) {
|
|
318
|
-
throw new Error(`Failed to refresh token: ${resJson["error"]}`);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const access_token = resJson["access_token"] as string;
|
|
322
|
-
const refresh_token = resJson["refresh_token"] as string | undefined;
|
|
323
|
-
const expires = resJson["expires"] as number;
|
|
324
|
-
const expiration_refresh = resJson["expiration_refresh"] as number | undefined;
|
|
325
|
-
|
|
326
|
-
if (access_token) {
|
|
327
|
-
this.auth_info.token = access_token;
|
|
328
|
-
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
329
|
-
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
330
|
-
|
|
331
|
-
// Update refresh token if a new one is provided
|
|
332
|
-
if (refresh_token) {
|
|
333
|
-
this.auth_info.refresh_token = refresh_token;
|
|
334
|
-
}
|
|
335
|
-
if (expiration_refresh) {
|
|
336
|
-
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
await this.saveTokens();
|
|
340
|
-
return true;
|
|
341
|
-
} else {
|
|
342
|
-
throw new Error("Failed to refresh token: no access token in response");
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Ensure the access token is valid, refreshing if necessary
|
|
348
|
-
*/
|
|
349
|
-
private ensureValidToken = async (): Promise<void> => {
|
|
350
|
-
if (this.isTokenExpired()) {
|
|
351
|
-
await this.refreshToken();
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Returns the logged in user's favorites, and all of their (visible) attributes.
|
|
357
|
-
*/
|
|
358
|
-
public get_watchlist = async (): Promise<WatchlistFull[]> => {
|
|
359
|
-
this.check_auth();
|
|
360
|
-
const res = await this.get(
|
|
361
|
-
`/auctions/customer/${this.auth_info["user_id"]}/active-auctions`
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
return (await res.json())["watchlist_full"] as WatchlistFull[];
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
/**
|
|
368
|
-
* Save tokens to file for persistence across restarts
|
|
369
|
-
*/
|
|
370
|
-
private saveTokens = async (): Promise<void> => {
|
|
371
|
-
if (!this.tokenFilePath) {
|
|
372
|
-
console.log("No token file path provided, skipping token save");
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
const tokenData = {
|
|
378
|
-
token: this.auth_info.token,
|
|
379
|
-
refresh_token: this.auth_info.refresh_token,
|
|
380
|
-
token_expiration: this.auth_info.token_expiration?.toISOString(),
|
|
381
|
-
refresh_token_expiration: this.auth_info.refresh_token_expiration?.toISOString(),
|
|
382
|
-
user_id: this.auth_info.user_id,
|
|
383
|
-
device_id: this.auth_info.device_id,
|
|
384
|
-
};
|
|
385
|
-
|
|
386
|
-
await fs.writeFile(this.tokenFilePath, JSON.stringify(tokenData, null, 2), "utf-8");
|
|
387
|
-
console.log("Tokens saved to file:", this.tokenFilePath);
|
|
388
|
-
} catch (error) {
|
|
389
|
-
console.warn("Failed to save tokens:", error);
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Load tokens from file
|
|
395
|
-
*/
|
|
396
|
-
public static loadTokens = async (tokenFilePath: string): Promise<Partial<AuthInfo> | null> => {
|
|
397
|
-
try {
|
|
398
|
-
const data = await fs.readFile(tokenFilePath, "utf-8");
|
|
399
|
-
const tokenData = JSON.parse(data) as {
|
|
400
|
-
token?: string;
|
|
401
|
-
refresh_token?: string;
|
|
402
|
-
token_expiration?: string;
|
|
403
|
-
refresh_token_expiration?: string;
|
|
404
|
-
user_id?: string;
|
|
405
|
-
device_id?: string;
|
|
406
|
-
};
|
|
407
|
-
|
|
408
|
-
return {
|
|
409
|
-
token: tokenData.token,
|
|
410
|
-
refresh_token: tokenData.refresh_token,
|
|
411
|
-
token_expiration: tokenData.token_expiration ? new Date(tokenData.token_expiration) : undefined,
|
|
412
|
-
refresh_token_expiration: tokenData.refresh_token_expiration ? new Date(tokenData.refresh_token_expiration) : undefined,
|
|
413
|
-
user_id: tokenData.user_id,
|
|
414
|
-
device_id: tokenData.device_id,
|
|
415
|
-
};
|
|
416
|
-
} catch (error) {
|
|
417
|
-
// File doesn't exist or is invalid, return null
|
|
418
|
-
return null;
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
export default MacBid;
|
package/index.ts
DELETED