macbid-ts-api 0.1.5 → 1.0.0-beta.2
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 +35 -2
- package/MacBid.d.ts.map +1 -1
- package/MacBid.js +213 -24
- package/MacBid.js.map +1 -1
- package/MacBid.ts +252 -20
- package/package.json +1 -1
package/MacBid.d.ts
CHANGED
|
@@ -3,7 +3,11 @@ export interface AuthInfo {
|
|
|
3
3
|
email?: string;
|
|
4
4
|
password?: string;
|
|
5
5
|
token?: string;
|
|
6
|
+
token_expiration?: Date;
|
|
6
7
|
user_id?: string;
|
|
8
|
+
refresh_token?: string;
|
|
9
|
+
refresh_token_expiration?: Date;
|
|
10
|
+
validation_code?: string;
|
|
7
11
|
}
|
|
8
12
|
export interface WatchlistFull {
|
|
9
13
|
auction_lot_id: number;
|
|
@@ -60,7 +64,8 @@ export declare class MacBid {
|
|
|
60
64
|
API_ROOT: string;
|
|
61
65
|
private macbid_session_headers;
|
|
62
66
|
private auth_info;
|
|
63
|
-
|
|
67
|
+
private tokenFilePath?;
|
|
68
|
+
constructor(auth_info: AuthInfo, tokenFilePath?: string);
|
|
64
69
|
authenticate: () => Promise<void>;
|
|
65
70
|
get: (path: string) => Promise<MacBidApiResponse>;
|
|
66
71
|
post: (path: string, options?: RequestInit) => Promise<MacBidApiResponse>;
|
|
@@ -70,12 +75,40 @@ export declare class MacBid {
|
|
|
70
75
|
private check_auth;
|
|
71
76
|
/**
|
|
72
77
|
* Do the login request
|
|
78
|
+
* @param email - User email
|
|
79
|
+
* @param password - User password
|
|
80
|
+
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
73
81
|
*/
|
|
74
|
-
login: (email: string, password: string) => Promise<boolean>;
|
|
82
|
+
login: (email: string, password: string, validation_code?: string) => Promise<boolean>;
|
|
83
|
+
get_refresh_token_expiration: () => Date;
|
|
84
|
+
/**
|
|
85
|
+
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
86
|
+
*/
|
|
87
|
+
private isTokenExpired;
|
|
88
|
+
/**
|
|
89
|
+
* Check if the refresh token is expired
|
|
90
|
+
*/
|
|
91
|
+
private isRefreshTokenExpired;
|
|
92
|
+
/**
|
|
93
|
+
* Refresh the access token using the refresh token
|
|
94
|
+
*/
|
|
95
|
+
refreshToken: () => Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Ensure the access token is valid, refreshing if necessary
|
|
98
|
+
*/
|
|
99
|
+
private ensureValidToken;
|
|
75
100
|
/**
|
|
76
101
|
* Returns the logged in user's favorites, and all of their (visible) attributes.
|
|
77
102
|
*/
|
|
78
103
|
get_watchlist: () => Promise<WatchlistFull[]>;
|
|
104
|
+
/**
|
|
105
|
+
* Save tokens to file for persistence across restarts
|
|
106
|
+
*/
|
|
107
|
+
private saveTokens;
|
|
108
|
+
/**
|
|
109
|
+
* Load tokens from file
|
|
110
|
+
*/
|
|
111
|
+
static loadTokens: (tokenFilePath: string) => Promise<Partial<AuthInfo> | null>;
|
|
79
112
|
}
|
|
80
113
|
export default MacBid;
|
|
81
114
|
//# sourceMappingURL=MacBid.d.ts.map
|
package/MacBid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MacBid.d.ts","sourceRoot":"","sources":["MacBid.ts"],"names":[],"mappings":"AAAA,OAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"MacBid.d.ts","sourceRoot":"","sources":["MacBid.ts"],"names":[],"mappings":"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"}
|
package/MacBid.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fetch from "node-fetch";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
2
3
|
export var AuctionType;
|
|
3
4
|
(function (AuctionType) {
|
|
4
5
|
AuctionType["Pallet"] = "pallet";
|
|
@@ -17,36 +18,60 @@ export class MacBid {
|
|
|
17
18
|
"Content-Type": "application/json",
|
|
18
19
|
};
|
|
19
20
|
auth_info;
|
|
20
|
-
|
|
21
|
+
tokenFilePath;
|
|
22
|
+
constructor(auth_info, tokenFilePath) {
|
|
21
23
|
this.auth_info = auth_info;
|
|
24
|
+
this.tokenFilePath = tokenFilePath;
|
|
22
25
|
this.authenticate();
|
|
23
26
|
}
|
|
24
27
|
authenticate = async () => {
|
|
25
28
|
if (this.auth_info) {
|
|
29
|
+
// If we have a token, check if it's still valid
|
|
26
30
|
if (this.auth_info.token) {
|
|
27
31
|
this.macbid_session_headers["Authorization"] = this.auth_info
|
|
28
32
|
.token;
|
|
33
|
+
// Token will be auto-refreshed by ensureValidToken if needed
|
|
34
|
+
return;
|
|
29
35
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
// If we have a refresh token but no access token, try to refresh first
|
|
37
|
+
if (this.auth_info.refresh_token && !this.isRefreshTokenExpired()) {
|
|
38
|
+
try {
|
|
39
|
+
await this.refreshToken();
|
|
40
|
+
return;
|
|
33
41
|
}
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
catch (error) {
|
|
43
|
+
// If refresh fails, fall through to login
|
|
44
|
+
console.warn("Failed to refresh token, attempting login:", error);
|
|
36
45
|
}
|
|
37
46
|
}
|
|
47
|
+
// No valid tokens, need to login
|
|
48
|
+
if (this.auth_info.email && this.auth_info.password) {
|
|
49
|
+
await this.login(this.auth_info.email, this.auth_info.password);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw new Error("Invalid auth_info");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
get = async (path) => {
|
|
57
|
+
await this.ensureValidToken();
|
|
58
|
+
return (await fetch(this.API_ROOT + path, {
|
|
59
|
+
headers: this.macbid_session_headers,
|
|
60
|
+
}));
|
|
61
|
+
};
|
|
62
|
+
post = async (path, options) => {
|
|
63
|
+
// Don't refresh token for auth endpoints
|
|
64
|
+
if (!path.includes("/auth/")) {
|
|
65
|
+
await this.ensureValidToken();
|
|
38
66
|
}
|
|
67
|
+
return (await fetch(this.API_ROOT + path, {
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
headers: this.macbid_session_headers,
|
|
71
|
+
method: "POST",
|
|
72
|
+
...options,
|
|
73
|
+
}));
|
|
39
74
|
};
|
|
40
|
-
get = async (path) => (await fetch(this.API_ROOT + path, {
|
|
41
|
-
headers: this.macbid_session_headers,
|
|
42
|
-
}));
|
|
43
|
-
post = async (path, options) => (await fetch(this.API_ROOT + path, {
|
|
44
|
-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
45
|
-
// @ts-ignore
|
|
46
|
-
headers: this.macbid_session_headers,
|
|
47
|
-
method: "POST",
|
|
48
|
-
...options,
|
|
49
|
-
}));
|
|
50
75
|
/**
|
|
51
76
|
* Raise an exception if an endpoint requiring login is called without valid auth
|
|
52
77
|
*/
|
|
@@ -58,26 +83,149 @@ export class MacBid {
|
|
|
58
83
|
};
|
|
59
84
|
/**
|
|
60
85
|
* Do the login request
|
|
86
|
+
* @param email - User email
|
|
87
|
+
* @param password - User password
|
|
88
|
+
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
61
89
|
*/
|
|
62
|
-
login = async (email, password) => {
|
|
90
|
+
login = async (email, password, validation_code) => {
|
|
63
91
|
const login_params = {
|
|
92
|
+
device_id: crypto.randomUUID(),
|
|
64
93
|
email: email,
|
|
65
94
|
password: password,
|
|
95
|
+
ref_code: null,
|
|
96
|
+
ref_r: null,
|
|
97
|
+
remember_me: true,
|
|
98
|
+
utm_campaign: null,
|
|
99
|
+
utm_medium: null,
|
|
100
|
+
utm_source: null,
|
|
66
101
|
};
|
|
67
|
-
|
|
102
|
+
// https://api.macdiscount.com/auth/auth-validation
|
|
103
|
+
const res = await this.post("/auth/auth-validation", {
|
|
68
104
|
body: JSON.stringify(login_params),
|
|
69
105
|
});
|
|
70
106
|
const resJson = await res.json();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
107
|
+
if (resJson["message"] === "Login validation code sent") {
|
|
108
|
+
// Get validation code from parameter, auth_info, or throw error
|
|
109
|
+
const code = validation_code ||
|
|
110
|
+
this.auth_info.validation_code ||
|
|
111
|
+
process.env.MACBID_VALIDATION_CODE;
|
|
112
|
+
if (!code) {
|
|
113
|
+
throw new Error("Validation code required. Provide it via:\n" +
|
|
114
|
+
" 1. login() method parameter: login(email, password, validation_code)\n" +
|
|
115
|
+
" 2. AuthInfo.validation_code when creating MacBid instance\n" +
|
|
116
|
+
" 3. MACBID_VALIDATION_CODE environment variable");
|
|
117
|
+
}
|
|
118
|
+
const validation_params = {
|
|
119
|
+
code: code,
|
|
120
|
+
device_id: login_params.device_id,
|
|
121
|
+
new_password: "",
|
|
122
|
+
remember_me: true,
|
|
123
|
+
};
|
|
124
|
+
const validation_res = await this.post("/auth/validate-access-code", {
|
|
125
|
+
body: JSON.stringify(validation_params),
|
|
126
|
+
});
|
|
127
|
+
const validation_resJson = await validation_res.json();
|
|
128
|
+
const access_token = validation_resJson["access_token"];
|
|
129
|
+
const refresh_token = validation_resJson["refresh_token"];
|
|
130
|
+
const user_id = validation_resJson["user_id"];
|
|
131
|
+
const expires = validation_resJson["expires"];
|
|
132
|
+
const expiration_refresh = validation_resJson["expiration_refresh"];
|
|
133
|
+
if (access_token) {
|
|
134
|
+
this.auth_info.token = access_token;
|
|
135
|
+
this.auth_info.user_id = user_id;
|
|
136
|
+
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
137
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
138
|
+
this.auth_info.refresh_token = refresh_token;
|
|
139
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
140
|
+
await this.saveTokens();
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
console.error(JSON.stringify(resJson, null, 2));
|
|
145
|
+
throw new Error("Login failed");
|
|
146
|
+
};
|
|
147
|
+
get_refresh_token_expiration = () => {
|
|
148
|
+
if (this.auth_info.refresh_token_expiration) {
|
|
149
|
+
return this.auth_info.refresh_token_expiration;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
throw new Error("Refresh token expiration not set, make sure to login first.");
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
157
|
+
*/
|
|
158
|
+
isTokenExpired = () => {
|
|
159
|
+
if (!this.auth_info.token_expiration) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
// Refresh if token expires within 5 minutes
|
|
163
|
+
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
164
|
+
return Date.now() >= this.auth_info.token_expiration.getTime() - bufferTime;
|
|
165
|
+
};
|
|
166
|
+
/**
|
|
167
|
+
* Check if the refresh token is expired
|
|
168
|
+
*/
|
|
169
|
+
isRefreshTokenExpired = () => {
|
|
170
|
+
if (!this.auth_info.refresh_token_expiration) {
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
return Date.now() >= this.auth_info.refresh_token_expiration.getTime();
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Refresh the access token using the refresh token
|
|
177
|
+
*/
|
|
178
|
+
refreshToken = async () => {
|
|
179
|
+
if (!this.auth_info.refresh_token) {
|
|
180
|
+
throw new Error("No refresh token available. Please login again.");
|
|
181
|
+
}
|
|
182
|
+
if (this.isRefreshTokenExpired()) {
|
|
183
|
+
throw new Error("Refresh token has expired. Please login again.");
|
|
184
|
+
}
|
|
185
|
+
const refresh_params = {
|
|
186
|
+
refresh_token: this.auth_info.refresh_token,
|
|
187
|
+
};
|
|
188
|
+
// Use fetch directly to avoid triggering ensureValidToken and use PUT method
|
|
189
|
+
const res = await fetch(this.API_ROOT + "/auth/refresh-token", {
|
|
190
|
+
method: "PUT",
|
|
191
|
+
body: JSON.stringify(refresh_params),
|
|
192
|
+
headers: {
|
|
193
|
+
"Content-Type": "application/json",
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
const resJson = (await res.json());
|
|
197
|
+
// Check for error response
|
|
198
|
+
if (resJson["error"]) {
|
|
199
|
+
throw new Error(`Failed to refresh token: ${resJson["error"]}`);
|
|
200
|
+
}
|
|
201
|
+
const access_token = resJson["access_token"];
|
|
202
|
+
const refresh_token = resJson["refresh_token"];
|
|
203
|
+
const expires = resJson["expires"];
|
|
204
|
+
const expiration_refresh = resJson["expiration_refresh"];
|
|
205
|
+
if (access_token) {
|
|
206
|
+
this.auth_info.token = access_token;
|
|
76
207
|
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
208
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
209
|
+
// Update refresh token if a new one is provided
|
|
210
|
+
if (refresh_token) {
|
|
211
|
+
this.auth_info.refresh_token = refresh_token;
|
|
212
|
+
}
|
|
213
|
+
if (expiration_refresh) {
|
|
214
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
215
|
+
}
|
|
216
|
+
await this.saveTokens();
|
|
77
217
|
return true;
|
|
78
218
|
}
|
|
79
219
|
else {
|
|
80
|
-
throw new Error("
|
|
220
|
+
throw new Error("Failed to refresh token: no access token in response");
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Ensure the access token is valid, refreshing if necessary
|
|
225
|
+
*/
|
|
226
|
+
ensureValidToken = async () => {
|
|
227
|
+
if (this.isTokenExpired()) {
|
|
228
|
+
await this.refreshToken();
|
|
81
229
|
}
|
|
82
230
|
};
|
|
83
231
|
/**
|
|
@@ -88,6 +236,47 @@ export class MacBid {
|
|
|
88
236
|
const res = await this.get(`/auctions/customer/${this.auth_info["user_id"]}/active-auctions`);
|
|
89
237
|
return (await res.json())["watchlist_full"];
|
|
90
238
|
};
|
|
239
|
+
/**
|
|
240
|
+
* Save tokens to file for persistence across restarts
|
|
241
|
+
*/
|
|
242
|
+
saveTokens = async () => {
|
|
243
|
+
if (!this.tokenFilePath) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
const tokenData = {
|
|
248
|
+
token: this.auth_info.token,
|
|
249
|
+
refresh_token: this.auth_info.refresh_token,
|
|
250
|
+
token_expiration: this.auth_info.token_expiration?.toISOString(),
|
|
251
|
+
refresh_token_expiration: this.auth_info.refresh_token_expiration?.toISOString(),
|
|
252
|
+
user_id: this.auth_info.user_id,
|
|
253
|
+
};
|
|
254
|
+
await fs.writeFile(this.tokenFilePath, JSON.stringify(tokenData, null, 2), "utf-8");
|
|
255
|
+
}
|
|
256
|
+
catch (error) {
|
|
257
|
+
console.warn("Failed to save tokens:", error);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
/**
|
|
261
|
+
* Load tokens from file
|
|
262
|
+
*/
|
|
263
|
+
static loadTokens = async (tokenFilePath) => {
|
|
264
|
+
try {
|
|
265
|
+
const data = await fs.readFile(tokenFilePath, "utf-8");
|
|
266
|
+
const tokenData = JSON.parse(data);
|
|
267
|
+
return {
|
|
268
|
+
token: tokenData.token,
|
|
269
|
+
refresh_token: tokenData.refresh_token,
|
|
270
|
+
token_expiration: tokenData.token_expiration ? new Date(tokenData.token_expiration) : undefined,
|
|
271
|
+
refresh_token_expiration: tokenData.refresh_token_expiration ? new Date(tokenData.refresh_token_expiration) : undefined,
|
|
272
|
+
user_id: tokenData.user_id,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
// File doesn't exist or is invalid, return null
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
91
280
|
}
|
|
92
281
|
export default MacBid;
|
|
93
282
|
//# sourceMappingURL=MacBid.js.map
|
package/MacBid.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MacBid.js","sourceRoot":"","sources":["MacBid.ts"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"MacBid.js","sourceRoot":"","sources":["MacBid.ts"],"names":[],"mappings":"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"}
|
package/MacBid.ts
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import fetch, { Response } from "node-fetch";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
2
3
|
|
|
3
4
|
export interface AuthInfo {
|
|
4
5
|
email?: string;
|
|
5
6
|
password?: string;
|
|
6
7
|
token?: string;
|
|
8
|
+
token_expiration?: Date;
|
|
7
9
|
user_id?: string;
|
|
10
|
+
refresh_token?: string;
|
|
11
|
+
refresh_token_expiration?: Date;
|
|
12
|
+
validation_code?: string;
|
|
8
13
|
}
|
|
9
14
|
|
|
10
15
|
export interface WatchlistFull {
|
|
@@ -69,43 +74,67 @@ export class MacBid {
|
|
|
69
74
|
"Content-Type": "application/json",
|
|
70
75
|
};
|
|
71
76
|
private auth_info: AuthInfo;
|
|
77
|
+
private tokenFilePath?: string;
|
|
72
78
|
|
|
73
|
-
constructor(auth_info: AuthInfo) {
|
|
79
|
+
constructor(auth_info: AuthInfo, tokenFilePath?: string) {
|
|
74
80
|
this.auth_info = auth_info;
|
|
81
|
+
this.tokenFilePath = tokenFilePath;
|
|
75
82
|
this.authenticate();
|
|
76
83
|
}
|
|
77
84
|
|
|
78
85
|
public authenticate = async () => {
|
|
79
86
|
if (this.auth_info) {
|
|
87
|
+
// If we have a token, check if it's still valid
|
|
80
88
|
if (this.auth_info.token) {
|
|
81
89
|
this.macbid_session_headers["Authorization"] = this.auth_info
|
|
82
90
|
.token as string;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
// Token will be auto-refreshed by ensureValidToken if needed
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// If we have a refresh token but no access token, try to refresh first
|
|
96
|
+
if (this.auth_info.refresh_token && !this.isRefreshTokenExpired()) {
|
|
97
|
+
try {
|
|
98
|
+
await this.refreshToken();
|
|
99
|
+
return;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// If refresh fails, fall through to login
|
|
102
|
+
console.warn("Failed to refresh token, attempting login:", error);
|
|
88
103
|
}
|
|
89
104
|
}
|
|
105
|
+
|
|
106
|
+
// No valid tokens, need to login
|
|
107
|
+
if (this.auth_info.email && this.auth_info.password) {
|
|
108
|
+
await this.login(this.auth_info.email, this.auth_info.password);
|
|
109
|
+
} else {
|
|
110
|
+
throw new Error("Invalid auth_info");
|
|
111
|
+
}
|
|
90
112
|
}
|
|
91
113
|
};
|
|
92
114
|
|
|
93
|
-
public get = async (path: string): Promise<MacBidApiResponse> =>
|
|
94
|
-
|
|
115
|
+
public get = async (path: string): Promise<MacBidApiResponse> => {
|
|
116
|
+
await this.ensureValidToken();
|
|
117
|
+
return (await fetch(this.API_ROOT + path, {
|
|
95
118
|
headers: this.macbid_session_headers,
|
|
96
119
|
})) as MacBidApiResponse;
|
|
120
|
+
};
|
|
97
121
|
|
|
98
122
|
public post = async (
|
|
99
123
|
path: string,
|
|
100
124
|
options?: RequestInit
|
|
101
|
-
): Promise<MacBidApiResponse> =>
|
|
102
|
-
|
|
125
|
+
): Promise<MacBidApiResponse> => {
|
|
126
|
+
// Don't refresh token for auth endpoints
|
|
127
|
+
if (!path.includes("/auth/")) {
|
|
128
|
+
await this.ensureValidToken();
|
|
129
|
+
}
|
|
130
|
+
return (await fetch(this.API_ROOT + path, {
|
|
103
131
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
104
132
|
// @ts-ignore
|
|
105
133
|
headers: this.macbid_session_headers,
|
|
106
134
|
method: "POST",
|
|
107
135
|
...options,
|
|
108
136
|
})) as MacBidApiResponse;
|
|
137
|
+
};
|
|
109
138
|
|
|
110
139
|
/**
|
|
111
140
|
* Raise an exception if an endpoint requiring login is called without valid auth
|
|
@@ -119,31 +148,184 @@ export class MacBid {
|
|
|
119
148
|
|
|
120
149
|
/**
|
|
121
150
|
* Do the login request
|
|
151
|
+
* @param email - User email
|
|
152
|
+
* @param password - User password
|
|
153
|
+
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
122
154
|
*/
|
|
123
|
-
public login = async (
|
|
155
|
+
public login = async (
|
|
156
|
+
email: string,
|
|
157
|
+
password: string,
|
|
158
|
+
validation_code?: string
|
|
159
|
+
): Promise<boolean> => {
|
|
124
160
|
const login_params = {
|
|
161
|
+
device_id: crypto.randomUUID(), // TODO: keep this the same to persist sms validation?
|
|
125
162
|
email: email,
|
|
126
163
|
password: password,
|
|
164
|
+
ref_code: null,
|
|
165
|
+
ref_r: null,
|
|
166
|
+
remember_me: true,
|
|
167
|
+
utm_campaign: null,
|
|
168
|
+
utm_medium: null,
|
|
169
|
+
utm_source: null,
|
|
127
170
|
};
|
|
128
|
-
|
|
129
|
-
const res = await this.post("/
|
|
171
|
+
// https://api.macdiscount.com/auth/auth-validation
|
|
172
|
+
const res = await this.post("/auth/auth-validation", {
|
|
130
173
|
body: JSON.stringify(login_params),
|
|
131
174
|
});
|
|
132
175
|
|
|
133
176
|
const resJson = await res.json();
|
|
134
|
-
const token = resJson["token"];
|
|
135
|
-
const user_id = resJson["user_id"];
|
|
136
177
|
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
178
|
+
if (resJson["message"] === "Login validation code sent") {
|
|
179
|
+
// Get validation code from parameter, auth_info, or throw error
|
|
180
|
+
const code =
|
|
181
|
+
validation_code ||
|
|
182
|
+
this.auth_info.validation_code ||
|
|
183
|
+
process.env.MACBID_VALIDATION_CODE;
|
|
184
|
+
|
|
185
|
+
if (!code) {
|
|
186
|
+
throw new Error(
|
|
187
|
+
"Validation code required. Provide it via:\n" +
|
|
188
|
+
" 1. login() method parameter: login(email, password, validation_code)\n" +
|
|
189
|
+
" 2. AuthInfo.validation_code when creating MacBid instance\n" +
|
|
190
|
+
" 3. MACBID_VALIDATION_CODE environment variable"
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const validation_params = {
|
|
195
|
+
code: code,
|
|
196
|
+
device_id: login_params.device_id,
|
|
197
|
+
new_password: "",
|
|
198
|
+
remember_me: true,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
const validation_res = await this.post("/auth/validate-access-code", {
|
|
202
|
+
body: JSON.stringify(validation_params),
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const validation_resJson = await validation_res.json();
|
|
206
|
+
|
|
207
|
+
const access_token = validation_resJson["access_token"] as string;
|
|
208
|
+
const refresh_token = validation_resJson["refresh_token"] as string;
|
|
209
|
+
const user_id = validation_resJson["user_id"] as string;
|
|
210
|
+
const expires = validation_resJson["expires"] as number;
|
|
211
|
+
const expiration_refresh = validation_resJson["expiration_refresh"] as number;
|
|
212
|
+
|
|
213
|
+
if (access_token) {
|
|
214
|
+
this.auth_info.token = access_token as string;
|
|
215
|
+
this.auth_info.user_id = user_id as string;
|
|
216
|
+
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
217
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
218
|
+
this.auth_info.refresh_token = refresh_token;
|
|
219
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
220
|
+
await this.saveTokens();
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
console.error(JSON.stringify(resJson, null, 2));
|
|
225
|
+
throw new Error("Login failed");
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
public get_refresh_token_expiration = (): Date => {
|
|
229
|
+
if (this.auth_info.refresh_token_expiration) {
|
|
230
|
+
return this.auth_info.refresh_token_expiration;
|
|
231
|
+
} else {
|
|
232
|
+
throw new Error("Refresh token expiration not set, make sure to login first.");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
238
|
+
*/
|
|
239
|
+
private isTokenExpired = (): boolean => {
|
|
240
|
+
if (!this.auth_info.token_expiration) {
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
// Refresh if token expires within 5 minutes
|
|
244
|
+
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
245
|
+
return Date.now() >= this.auth_info.token_expiration.getTime() - bufferTime;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Check if the refresh token is expired
|
|
250
|
+
*/
|
|
251
|
+
private isRefreshTokenExpired = (): boolean => {
|
|
252
|
+
if (!this.auth_info.refresh_token_expiration) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
return Date.now() >= this.auth_info.refresh_token_expiration.getTime();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Refresh the access token using the refresh token
|
|
260
|
+
*/
|
|
261
|
+
public refreshToken = async (): Promise<boolean> => {
|
|
262
|
+
if (!this.auth_info.refresh_token) {
|
|
263
|
+
throw new Error("No refresh token available. Please login again.");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.isRefreshTokenExpired()) {
|
|
267
|
+
throw new Error("Refresh token has expired. Please login again.");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const refresh_params = {
|
|
271
|
+
refresh_token: this.auth_info.refresh_token,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// Use fetch directly to avoid triggering ensureValidToken and use PUT method
|
|
275
|
+
const res = await fetch(this.API_ROOT + "/auth/refresh-token", {
|
|
276
|
+
method: "PUT",
|
|
277
|
+
body: JSON.stringify(refresh_params),
|
|
278
|
+
headers: {
|
|
279
|
+
"Content-Type": "application/json",
|
|
280
|
+
},
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const resJson = (await res.json()) as {
|
|
284
|
+
error?: string;
|
|
285
|
+
access_token?: string;
|
|
286
|
+
refresh_token?: string;
|
|
287
|
+
expires?: number;
|
|
288
|
+
expiration_refresh?: number;
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Check for error response
|
|
292
|
+
if (resJson["error"]) {
|
|
293
|
+
throw new Error(`Failed to refresh token: ${resJson["error"]}`);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const access_token = resJson["access_token"] as string;
|
|
297
|
+
const refresh_token = resJson["refresh_token"] as string | undefined;
|
|
298
|
+
const expires = resJson["expires"] as number;
|
|
299
|
+
const expiration_refresh = resJson["expiration_refresh"] as number | undefined;
|
|
300
|
+
|
|
301
|
+
if (access_token) {
|
|
302
|
+
this.auth_info.token = access_token;
|
|
140
303
|
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
304
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
141
305
|
|
|
306
|
+
// Update refresh token if a new one is provided
|
|
307
|
+
if (refresh_token) {
|
|
308
|
+
this.auth_info.refresh_token = refresh_token;
|
|
309
|
+
}
|
|
310
|
+
if (expiration_refresh) {
|
|
311
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
await this.saveTokens();
|
|
142
315
|
return true;
|
|
143
316
|
} else {
|
|
144
|
-
throw new Error("
|
|
317
|
+
throw new Error("Failed to refresh token: no access token in response");
|
|
145
318
|
}
|
|
146
|
-
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Ensure the access token is valid, refreshing if necessary
|
|
323
|
+
*/
|
|
324
|
+
private ensureValidToken = async (): Promise<void> => {
|
|
325
|
+
if (this.isTokenExpired()) {
|
|
326
|
+
await this.refreshToken();
|
|
327
|
+
}
|
|
328
|
+
}
|
|
147
329
|
|
|
148
330
|
/**
|
|
149
331
|
* Returns the logged in user's favorites, and all of their (visible) attributes.
|
|
@@ -156,6 +338,56 @@ export class MacBid {
|
|
|
156
338
|
|
|
157
339
|
return (await res.json())["watchlist_full"] as WatchlistFull[];
|
|
158
340
|
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Save tokens to file for persistence across restarts
|
|
344
|
+
*/
|
|
345
|
+
private saveTokens = async (): Promise<void> => {
|
|
346
|
+
if (!this.tokenFilePath) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
try {
|
|
351
|
+
const tokenData = {
|
|
352
|
+
token: this.auth_info.token,
|
|
353
|
+
refresh_token: this.auth_info.refresh_token,
|
|
354
|
+
token_expiration: this.auth_info.token_expiration?.toISOString(),
|
|
355
|
+
refresh_token_expiration: this.auth_info.refresh_token_expiration?.toISOString(),
|
|
356
|
+
user_id: this.auth_info.user_id,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
await fs.writeFile(this.tokenFilePath, JSON.stringify(tokenData, null, 2), "utf-8");
|
|
360
|
+
} catch (error) {
|
|
361
|
+
console.warn("Failed to save tokens:", error);
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Load tokens from file
|
|
367
|
+
*/
|
|
368
|
+
public static loadTokens = async (tokenFilePath: string): Promise<Partial<AuthInfo> | null> => {
|
|
369
|
+
try {
|
|
370
|
+
const data = await fs.readFile(tokenFilePath, "utf-8");
|
|
371
|
+
const tokenData = JSON.parse(data) as {
|
|
372
|
+
token?: string;
|
|
373
|
+
refresh_token?: string;
|
|
374
|
+
token_expiration?: string;
|
|
375
|
+
refresh_token_expiration?: string;
|
|
376
|
+
user_id?: string;
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
token: tokenData.token,
|
|
381
|
+
refresh_token: tokenData.refresh_token,
|
|
382
|
+
token_expiration: tokenData.token_expiration ? new Date(tokenData.token_expiration) : undefined,
|
|
383
|
+
refresh_token_expiration: tokenData.refresh_token_expiration ? new Date(tokenData.refresh_token_expiration) : undefined,
|
|
384
|
+
user_id: tokenData.user_id,
|
|
385
|
+
};
|
|
386
|
+
} catch (error) {
|
|
387
|
+
// File doesn't exist or is invalid, return null
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
};
|
|
159
391
|
}
|
|
160
392
|
|
|
161
393
|
export default MacBid;
|