macbid-ts-api 0.1.4 → 1.0.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 +25 -1
- package/MacBid.d.ts.map +1 -1
- package/MacBid.js +148 -22
- package/MacBid.js.map +1 -1
- package/MacBid.ts +176 -14
- 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;
|
|
@@ -70,8 +74,28 @@ export declare class MacBid {
|
|
|
70
74
|
private check_auth;
|
|
71
75
|
/**
|
|
72
76
|
* Do the login request
|
|
77
|
+
* @param email - User email
|
|
78
|
+
* @param password - User password
|
|
79
|
+
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
73
80
|
*/
|
|
74
|
-
login: (email: string, password: string) => Promise<boolean>;
|
|
81
|
+
login: (email: string, password: string, validation_code?: string) => Promise<boolean>;
|
|
82
|
+
get_refresh_token_expiration: () => Date;
|
|
83
|
+
/**
|
|
84
|
+
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
85
|
+
*/
|
|
86
|
+
private isTokenExpired;
|
|
87
|
+
/**
|
|
88
|
+
* Check if the refresh token is expired
|
|
89
|
+
*/
|
|
90
|
+
private isRefreshTokenExpired;
|
|
91
|
+
/**
|
|
92
|
+
* Refresh the access token using the refresh token
|
|
93
|
+
*/
|
|
94
|
+
refreshToken: () => Promise<boolean>;
|
|
95
|
+
/**
|
|
96
|
+
* Ensure the access token is valid, refreshing if necessary
|
|
97
|
+
*/
|
|
98
|
+
private ensureValidToken;
|
|
75
99
|
/**
|
|
76
100
|
* Returns the logged in user's favorites, and all of their (visible) attributes.
|
|
77
101
|
*/
|
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;AAE7C,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"MacBid.d.ts","sourceRoot":"","sources":["MacBid.ts"],"names":[],"mappings":"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"}
|
package/MacBid.js
CHANGED
|
@@ -15,8 +15,6 @@ export class MacBid {
|
|
|
15
15
|
API_ROOT = "https://api.macdiscount.com";
|
|
16
16
|
macbid_session_headers = {
|
|
17
17
|
"Content-Type": "application/json",
|
|
18
|
-
Origin: "https://mac.bid",
|
|
19
|
-
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0",
|
|
20
18
|
};
|
|
21
19
|
auth_info;
|
|
22
20
|
constructor(auth_info) {
|
|
@@ -39,16 +37,25 @@ export class MacBid {
|
|
|
39
37
|
}
|
|
40
38
|
}
|
|
41
39
|
};
|
|
42
|
-
get = async (path) =>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
get = async (path) => {
|
|
41
|
+
await this.ensureValidToken();
|
|
42
|
+
return (await fetch(this.API_ROOT + path, {
|
|
43
|
+
headers: this.macbid_session_headers,
|
|
44
|
+
}));
|
|
45
|
+
};
|
|
46
|
+
post = async (path, options) => {
|
|
47
|
+
// Don't refresh token for auth endpoints
|
|
48
|
+
if (!path.includes("/auth/")) {
|
|
49
|
+
await this.ensureValidToken();
|
|
50
|
+
}
|
|
51
|
+
return (await fetch(this.API_ROOT + path, {
|
|
52
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
headers: this.macbid_session_headers,
|
|
55
|
+
method: "POST",
|
|
56
|
+
...options,
|
|
57
|
+
}));
|
|
58
|
+
};
|
|
52
59
|
/**
|
|
53
60
|
* Raise an exception if an endpoint requiring login is called without valid auth
|
|
54
61
|
*/
|
|
@@ -60,28 +67,147 @@ export class MacBid {
|
|
|
60
67
|
};
|
|
61
68
|
/**
|
|
62
69
|
* Do the login request
|
|
70
|
+
* @param email - User email
|
|
71
|
+
* @param password - User password
|
|
72
|
+
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
63
73
|
*/
|
|
64
|
-
login = async (email, password) => {
|
|
74
|
+
login = async (email, password, validation_code) => {
|
|
65
75
|
const login_params = {
|
|
76
|
+
device_id: crypto.randomUUID(),
|
|
66
77
|
email: email,
|
|
67
78
|
password: password,
|
|
79
|
+
ref_code: null,
|
|
80
|
+
ref_r: null,
|
|
81
|
+
remember_me: true,
|
|
82
|
+
utm_campaign: null,
|
|
83
|
+
utm_medium: null,
|
|
84
|
+
utm_source: null,
|
|
68
85
|
};
|
|
69
|
-
|
|
86
|
+
// https://api.macdiscount.com/auth/auth-validation
|
|
87
|
+
const res = await this.post("/auth/auth-validation", {
|
|
70
88
|
body: JSON.stringify(login_params),
|
|
71
89
|
});
|
|
72
|
-
console.log(login_params);
|
|
73
|
-
console.log(res);
|
|
74
90
|
const resJson = await res.json();
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
91
|
+
if (resJson["message"] === "Login validation code sent") {
|
|
92
|
+
// Get validation code from parameter, auth_info, or throw error
|
|
93
|
+
const code = validation_code ||
|
|
94
|
+
this.auth_info.validation_code ||
|
|
95
|
+
process.env.MACBID_VALIDATION_CODE;
|
|
96
|
+
if (!code) {
|
|
97
|
+
throw new Error("Validation code required. Provide it via:\n" +
|
|
98
|
+
" 1. login() method parameter: login(email, password, validation_code)\n" +
|
|
99
|
+
" 2. AuthInfo.validation_code when creating MacBid instance\n" +
|
|
100
|
+
" 3. MACBID_VALIDATION_CODE environment variable");
|
|
101
|
+
}
|
|
102
|
+
const validation_params = {
|
|
103
|
+
code: code,
|
|
104
|
+
device_id: login_params.device_id,
|
|
105
|
+
new_password: "",
|
|
106
|
+
remember_me: true,
|
|
107
|
+
};
|
|
108
|
+
const validation_res = await this.post("/auth/validate-access-code", {
|
|
109
|
+
body: JSON.stringify(validation_params),
|
|
110
|
+
});
|
|
111
|
+
const validation_resJson = await validation_res.json();
|
|
112
|
+
const access_token = validation_resJson["access_token"];
|
|
113
|
+
const refresh_token = validation_resJson["refresh_token"];
|
|
114
|
+
const user_id = validation_resJson["user_id"];
|
|
115
|
+
const expires = validation_resJson["expires"];
|
|
116
|
+
const expiration_refresh = validation_resJson["expiration_refresh"];
|
|
117
|
+
if (access_token) {
|
|
118
|
+
this.auth_info.token = access_token;
|
|
119
|
+
this.auth_info.user_id = user_id;
|
|
120
|
+
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
121
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
122
|
+
this.auth_info.refresh_token = refresh_token;
|
|
123
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.error(JSON.stringify(resJson, null, 2));
|
|
128
|
+
throw new Error("Login failed");
|
|
129
|
+
};
|
|
130
|
+
get_refresh_token_expiration = () => {
|
|
131
|
+
if (this.auth_info.refresh_token_expiration) {
|
|
132
|
+
return this.auth_info.refresh_token_expiration;
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
throw new Error("Refresh token expiration not set, make sure to login first.");
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
140
|
+
*/
|
|
141
|
+
isTokenExpired = () => {
|
|
142
|
+
if (!this.auth_info.token_expiration) {
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
// Refresh if token expires within 5 minutes
|
|
146
|
+
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
147
|
+
return Date.now() >= this.auth_info.token_expiration.getTime() - bufferTime;
|
|
148
|
+
};
|
|
149
|
+
/**
|
|
150
|
+
* Check if the refresh token is expired
|
|
151
|
+
*/
|
|
152
|
+
isRefreshTokenExpired = () => {
|
|
153
|
+
if (!this.auth_info.refresh_token_expiration) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
return Date.now() >= this.auth_info.refresh_token_expiration.getTime();
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Refresh the access token using the refresh token
|
|
160
|
+
*/
|
|
161
|
+
refreshToken = async () => {
|
|
162
|
+
if (!this.auth_info.refresh_token) {
|
|
163
|
+
throw new Error("No refresh token available. Please login again.");
|
|
164
|
+
}
|
|
165
|
+
if (this.isRefreshTokenExpired()) {
|
|
166
|
+
throw new Error("Refresh token has expired. Please login again.");
|
|
167
|
+
}
|
|
168
|
+
const refresh_params = {
|
|
169
|
+
refresh_token: this.auth_info.refresh_token,
|
|
170
|
+
};
|
|
171
|
+
// Use fetch directly to avoid triggering ensureValidToken and use PUT method
|
|
172
|
+
const res = await fetch(this.API_ROOT + "/auth/refresh-token", {
|
|
173
|
+
method: "PUT",
|
|
174
|
+
body: JSON.stringify(refresh_params),
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const resJson = (await res.json());
|
|
180
|
+
// Check for error response
|
|
181
|
+
if (resJson["error"]) {
|
|
182
|
+
throw new Error(`Failed to refresh token: ${resJson["error"]}`);
|
|
183
|
+
}
|
|
184
|
+
const access_token = resJson["access_token"];
|
|
185
|
+
const refresh_token = resJson["refresh_token"];
|
|
186
|
+
const expires = resJson["expires"];
|
|
187
|
+
const expiration_refresh = resJson["expiration_refresh"];
|
|
188
|
+
if (access_token) {
|
|
189
|
+
this.auth_info.token = access_token;
|
|
80
190
|
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
191
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
192
|
+
// Update refresh token if a new one is provided
|
|
193
|
+
if (refresh_token) {
|
|
194
|
+
this.auth_info.refresh_token = refresh_token;
|
|
195
|
+
}
|
|
196
|
+
if (expiration_refresh) {
|
|
197
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
198
|
+
}
|
|
81
199
|
return true;
|
|
82
200
|
}
|
|
83
201
|
else {
|
|
84
|
-
throw new Error("
|
|
202
|
+
throw new Error("Failed to refresh token: no access token in response");
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
/**
|
|
206
|
+
* Ensure the access token is valid, refreshing if necessary
|
|
207
|
+
*/
|
|
208
|
+
ensureValidToken = async () => {
|
|
209
|
+
if (this.isTokenExpired()) {
|
|
210
|
+
await this.refreshToken();
|
|
85
211
|
}
|
|
86
212
|
};
|
|
87
213
|
/**
|
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
|
@@ -4,7 +4,11 @@ export interface AuthInfo {
|
|
|
4
4
|
email?: string;
|
|
5
5
|
password?: string;
|
|
6
6
|
token?: string;
|
|
7
|
+
token_expiration?: Date;
|
|
7
8
|
user_id?: string;
|
|
9
|
+
refresh_token?: string;
|
|
10
|
+
refresh_token_expiration?: Date;
|
|
11
|
+
validation_code?: string;
|
|
8
12
|
}
|
|
9
13
|
|
|
10
14
|
export interface WatchlistFull {
|
|
@@ -90,22 +94,29 @@ export class MacBid {
|
|
|
90
94
|
}
|
|
91
95
|
};
|
|
92
96
|
|
|
93
|
-
public get = async (path: string): Promise<MacBidApiResponse> =>
|
|
94
|
-
|
|
97
|
+
public get = async (path: string): Promise<MacBidApiResponse> => {
|
|
98
|
+
await this.ensureValidToken();
|
|
99
|
+
return (await fetch(this.API_ROOT + path, {
|
|
95
100
|
headers: this.macbid_session_headers,
|
|
96
101
|
})) as MacBidApiResponse;
|
|
102
|
+
};
|
|
97
103
|
|
|
98
104
|
public post = async (
|
|
99
105
|
path: string,
|
|
100
106
|
options?: RequestInit
|
|
101
|
-
): Promise<MacBidApiResponse> =>
|
|
102
|
-
|
|
107
|
+
): Promise<MacBidApiResponse> => {
|
|
108
|
+
// Don't refresh token for auth endpoints
|
|
109
|
+
if (!path.includes("/auth/")) {
|
|
110
|
+
await this.ensureValidToken();
|
|
111
|
+
}
|
|
112
|
+
return (await fetch(this.API_ROOT + path, {
|
|
103
113
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
104
114
|
// @ts-ignore
|
|
105
115
|
headers: this.macbid_session_headers,
|
|
106
116
|
method: "POST",
|
|
107
117
|
...options,
|
|
108
118
|
})) as MacBidApiResponse;
|
|
119
|
+
};
|
|
109
120
|
|
|
110
121
|
/**
|
|
111
122
|
* Raise an exception if an endpoint requiring login is called without valid auth
|
|
@@ -119,31 +130,182 @@ export class MacBid {
|
|
|
119
130
|
|
|
120
131
|
/**
|
|
121
132
|
* Do the login request
|
|
133
|
+
* @param email - User email
|
|
134
|
+
* @param password - User password
|
|
135
|
+
* @param validation_code - Optional validation code. If not provided, will check auth_info.validation_code
|
|
122
136
|
*/
|
|
123
|
-
public login = async (
|
|
137
|
+
public login = async (
|
|
138
|
+
email: string,
|
|
139
|
+
password: string,
|
|
140
|
+
validation_code?: string
|
|
141
|
+
): Promise<boolean> => {
|
|
124
142
|
const login_params = {
|
|
143
|
+
device_id: crypto.randomUUID(), // TODO: keep this the same to persist sms validation?
|
|
125
144
|
email: email,
|
|
126
145
|
password: password,
|
|
146
|
+
ref_code: null,
|
|
147
|
+
ref_r: null,
|
|
148
|
+
remember_me: true,
|
|
149
|
+
utm_campaign: null,
|
|
150
|
+
utm_medium: null,
|
|
151
|
+
utm_source: null,
|
|
127
152
|
};
|
|
128
|
-
|
|
129
|
-
const res = await this.post("/
|
|
153
|
+
// https://api.macdiscount.com/auth/auth-validation
|
|
154
|
+
const res = await this.post("/auth/auth-validation", {
|
|
130
155
|
body: JSON.stringify(login_params),
|
|
131
156
|
});
|
|
132
157
|
|
|
133
158
|
const resJson = await res.json();
|
|
134
|
-
const token = resJson["token"];
|
|
135
|
-
const user_id = resJson["user_id"];
|
|
136
159
|
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
if (resJson["message"] === "Login validation code sent") {
|
|
161
|
+
// Get validation code from parameter, auth_info, or throw error
|
|
162
|
+
const code =
|
|
163
|
+
validation_code ||
|
|
164
|
+
this.auth_info.validation_code ||
|
|
165
|
+
process.env.MACBID_VALIDATION_CODE;
|
|
166
|
+
|
|
167
|
+
if (!code) {
|
|
168
|
+
throw new Error(
|
|
169
|
+
"Validation code required. Provide it via:\n" +
|
|
170
|
+
" 1. login() method parameter: login(email, password, validation_code)\n" +
|
|
171
|
+
" 2. AuthInfo.validation_code when creating MacBid instance\n" +
|
|
172
|
+
" 3. MACBID_VALIDATION_CODE environment variable"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const validation_params = {
|
|
177
|
+
code: code,
|
|
178
|
+
device_id: login_params.device_id,
|
|
179
|
+
new_password: "",
|
|
180
|
+
remember_me: true,
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const validation_res = await this.post("/auth/validate-access-code", {
|
|
184
|
+
body: JSON.stringify(validation_params),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const validation_resJson = await validation_res.json();
|
|
188
|
+
|
|
189
|
+
const access_token = validation_resJson["access_token"] as string;
|
|
190
|
+
const refresh_token = validation_resJson["refresh_token"] as string;
|
|
191
|
+
const user_id = validation_resJson["user_id"] as string;
|
|
192
|
+
const expires = validation_resJson["expires"] as number;
|
|
193
|
+
const expiration_refresh = validation_resJson["expiration_refresh"] as number;
|
|
194
|
+
|
|
195
|
+
if (access_token) {
|
|
196
|
+
this.auth_info.token = access_token as string;
|
|
197
|
+
this.auth_info.user_id = user_id as string;
|
|
198
|
+
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
199
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
200
|
+
this.auth_info.refresh_token = refresh_token;
|
|
201
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
console.error(JSON.stringify(resJson, null, 2));
|
|
206
|
+
throw new Error("Login failed");
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
public get_refresh_token_expiration = (): Date => {
|
|
210
|
+
if (this.auth_info.refresh_token_expiration) {
|
|
211
|
+
return this.auth_info.refresh_token_expiration;
|
|
212
|
+
} else {
|
|
213
|
+
throw new Error("Refresh token expiration not set, make sure to login first.");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Check if the access token is expired or about to expire (within 5 minutes)
|
|
219
|
+
*/
|
|
220
|
+
private isTokenExpired = (): boolean => {
|
|
221
|
+
if (!this.auth_info.token_expiration) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
// Refresh if token expires within 5 minutes
|
|
225
|
+
const bufferTime = 5 * 60 * 1000; // 5 minutes in milliseconds
|
|
226
|
+
return Date.now() >= this.auth_info.token_expiration.getTime() - bufferTime;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if the refresh token is expired
|
|
231
|
+
*/
|
|
232
|
+
private isRefreshTokenExpired = (): boolean => {
|
|
233
|
+
if (!this.auth_info.refresh_token_expiration) {
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
return Date.now() >= this.auth_info.refresh_token_expiration.getTime();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Refresh the access token using the refresh token
|
|
241
|
+
*/
|
|
242
|
+
public refreshToken = async (): Promise<boolean> => {
|
|
243
|
+
if (!this.auth_info.refresh_token) {
|
|
244
|
+
throw new Error("No refresh token available. Please login again.");
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (this.isRefreshTokenExpired()) {
|
|
248
|
+
throw new Error("Refresh token has expired. Please login again.");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const refresh_params = {
|
|
252
|
+
refresh_token: this.auth_info.refresh_token,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Use fetch directly to avoid triggering ensureValidToken and use PUT method
|
|
256
|
+
const res = await fetch(this.API_ROOT + "/auth/refresh-token", {
|
|
257
|
+
method: "PUT",
|
|
258
|
+
body: JSON.stringify(refresh_params),
|
|
259
|
+
headers: {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
},
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
const resJson = (await res.json()) as {
|
|
265
|
+
error?: string;
|
|
266
|
+
access_token?: string;
|
|
267
|
+
refresh_token?: string;
|
|
268
|
+
expires?: number;
|
|
269
|
+
expiration_refresh?: number;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Check for error response
|
|
273
|
+
if (resJson["error"]) {
|
|
274
|
+
throw new Error(`Failed to refresh token: ${resJson["error"]}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const access_token = resJson["access_token"] as string;
|
|
278
|
+
const refresh_token = resJson["refresh_token"] as string | undefined;
|
|
279
|
+
const expires = resJson["expires"] as number;
|
|
280
|
+
const expiration_refresh = resJson["expiration_refresh"] as number | undefined;
|
|
281
|
+
|
|
282
|
+
if (access_token) {
|
|
283
|
+
this.auth_info.token = access_token;
|
|
140
284
|
this.macbid_session_headers["Authorization"] = this.auth_info.token;
|
|
285
|
+
this.auth_info.token_expiration = new Date(expires * 1000);
|
|
286
|
+
|
|
287
|
+
// Update refresh token if a new one is provided
|
|
288
|
+
if (refresh_token) {
|
|
289
|
+
this.auth_info.refresh_token = refresh_token;
|
|
290
|
+
}
|
|
291
|
+
if (expiration_refresh) {
|
|
292
|
+
this.auth_info.refresh_token_expiration = new Date(expiration_refresh * 1000);
|
|
293
|
+
}
|
|
141
294
|
|
|
142
295
|
return true;
|
|
143
296
|
} else {
|
|
144
|
-
throw new Error("
|
|
297
|
+
throw new Error("Failed to refresh token: no access token in response");
|
|
145
298
|
}
|
|
146
|
-
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Ensure the access token is valid, refreshing if necessary
|
|
303
|
+
*/
|
|
304
|
+
private ensureValidToken = async (): Promise<void> => {
|
|
305
|
+
if (this.isTokenExpired()) {
|
|
306
|
+
await this.refreshToken();
|
|
307
|
+
}
|
|
308
|
+
}
|
|
147
309
|
|
|
148
310
|
/**
|
|
149
311
|
* Returns the logged in user's favorites, and all of their (visible) attributes.
|