lightspeed-retail-sdk 3.3.3 → 3.3.5
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 +3 -3
- package/dist/src/core/LightspeedSDK.cjs +45 -10
- package/dist/src/core/LightspeedSDK.mjs +52 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A modern JavaScript SDK for interacting with the Lightspeed Retail API. This SDK provides a convenient, secure, and flexible way to access Lightspeed Retail's features—including customer, item, and order management.
|
|
4
4
|
|
|
5
|
-
**Current Version: 3.3.
|
|
5
|
+
**Current Version: 3.3.5** — improve error checking on token refresh to prevent false email warnings.
|
|
6
6
|
|
|
7
|
-
## **🆕 Recent Updates (v3.3.
|
|
7
|
+
## **🆕 Recent Updates (v3.3.5)**
|
|
8
8
|
|
|
9
9
|
- **Add centralized query param builder for API requests**: Add centralized query param builder for API requests. Supports input as object, string, or array, and manages relations/load_relations. Ensures no double-encoding of parameters and handles special cases for 'or' and 'timeStamp'.
|
|
10
10
|
- **🎯 Enhanced Parameter Support**: All main getter methods now support both legacy and new object-based parameters with full backward compatibility
|
|
@@ -66,7 +66,7 @@ const items = await sdk.getItems({
|
|
|
66
66
|
## Table of Contents
|
|
67
67
|
|
|
68
68
|
- [Another Unofficial Lightspeed Retail V3 API SDK](#another-unofficial-lightspeed-retail-v3-api-sdk)
|
|
69
|
-
- [**🆕 Recent Updates (v3.3.
|
|
69
|
+
- [**🆕 Recent Updates (v3.3.5)**](#-recent-updates-v335)
|
|
70
70
|
- [🚀 Key Features](#-key-features)
|
|
71
71
|
- [🔄 Migrating from 3.1.x](#-migrating-from-31x)
|
|
72
72
|
- [Backward Compatibility](#backward-compatibility)
|
|
@@ -207,8 +207,9 @@ class LightspeedSDKCore {
|
|
|
207
207
|
// Token management
|
|
208
208
|
async getToken() {
|
|
209
209
|
const now = new Date();
|
|
210
|
-
const bufferTime = 5 * 60 * 1000; // 5
|
|
210
|
+
const bufferTime = 5 * 60 * 1000; // 5-minute buffer
|
|
211
211
|
const storedTokens = await this.tokenStorage.getTokens();
|
|
212
|
+
// Check if the token is still valid
|
|
212
213
|
if (storedTokens.access_token && storedTokens.expires_at) {
|
|
213
214
|
const expiryTime = new Date(storedTokens.expires_at);
|
|
214
215
|
if (expiryTime.getTime() - now.getTime() > bufferTime) {
|
|
@@ -216,17 +217,35 @@ class LightspeedSDKCore {
|
|
|
216
217
|
return this.token;
|
|
217
218
|
}
|
|
218
219
|
}
|
|
219
|
-
|
|
220
|
-
if (
|
|
221
|
-
|
|
220
|
+
// Prevent concurrent refresh attempts
|
|
221
|
+
if (this.refreshInProgress) {
|
|
222
|
+
console.log("🔄 Token refresh already in progress. Waiting...");
|
|
223
|
+
while(this.refreshInProgress){
|
|
224
|
+
await sleep(100); // Wait for the ongoing refresh to complete
|
|
225
|
+
}
|
|
226
|
+
// After waiting, check if the token is now valid
|
|
227
|
+
const updatedTokens = await this.tokenStorage.getTokens();
|
|
228
|
+
if (updatedTokens.access_token && updatedTokens.expires_at) {
|
|
229
|
+
const expiryTime = new Date(updatedTokens.expires_at);
|
|
230
|
+
if (expiryTime.getTime() - now.getTime() > bufferTime) {
|
|
231
|
+
this.token = updatedTokens.access_token;
|
|
232
|
+
return this.token;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// If still invalid, proceed to refresh
|
|
222
236
|
}
|
|
223
|
-
|
|
224
|
-
grant_type: "refresh_token",
|
|
225
|
-
client_id: this.clientID,
|
|
226
|
-
client_secret: this.clientSecret,
|
|
227
|
-
refresh_token: refreshToken
|
|
228
|
-
};
|
|
237
|
+
this.refreshInProgress = true; // Lock the refresh process
|
|
229
238
|
try {
|
|
239
|
+
const refreshToken = storedTokens.refresh_token || this.refreshToken;
|
|
240
|
+
if (!refreshToken) {
|
|
241
|
+
throw new Error("No refresh token available");
|
|
242
|
+
}
|
|
243
|
+
const body = {
|
|
244
|
+
grant_type: "refresh_token",
|
|
245
|
+
client_id: this.clientID,
|
|
246
|
+
client_secret: this.clientSecret,
|
|
247
|
+
refresh_token: refreshToken
|
|
248
|
+
};
|
|
230
249
|
const response = await (0, _axios.default)({
|
|
231
250
|
url: this.tokenUrl,
|
|
232
251
|
method: "post",
|
|
@@ -237,6 +256,7 @@ class LightspeedSDKCore {
|
|
|
237
256
|
});
|
|
238
257
|
const tokenData = response.data;
|
|
239
258
|
const expiresAt = new Date(now.getTime() + tokenData.expires_in * 1000);
|
|
259
|
+
// Save the new tokens
|
|
240
260
|
await this.tokenStorage.setTokens({
|
|
241
261
|
access_token: tokenData.access_token,
|
|
242
262
|
refresh_token: tokenData.refresh_token,
|
|
@@ -245,8 +265,20 @@ class LightspeedSDKCore {
|
|
|
245
265
|
});
|
|
246
266
|
this.token = tokenData.access_token;
|
|
247
267
|
this.tokenExpiry = expiresAt;
|
|
268
|
+
console.log("✅ Token refresh successful");
|
|
248
269
|
return this.token;
|
|
249
270
|
} catch (error) {
|
|
271
|
+
console.error("❌ Token refresh failed:", error.message);
|
|
272
|
+
// Check if the token was refreshed by another process
|
|
273
|
+
const updatedTokens = await this.tokenStorage.getTokens();
|
|
274
|
+
if (updatedTokens.access_token && updatedTokens.expires_at) {
|
|
275
|
+
const expiryTime = new Date(updatedTokens.expires_at);
|
|
276
|
+
if (expiryTime.getTime() - now.getTime() > bufferTime) {
|
|
277
|
+
console.log("🔄 Token was refreshed by another process. Skipping failure email.");
|
|
278
|
+
return updatedTokens.access_token;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// If the token is still invalid, send the failure email
|
|
250
282
|
const helpMsg = "\n❌ Token refresh failed. Your refresh token may be expired or revoked.\n" + "👉 Please re-authenticate using the SDK's CLI or obtain a new refresh token.\n" + "Original error: " + ((error === null || error === void 0 ? void 0 : error.message) || error);
|
|
251
283
|
console.error(helpMsg);
|
|
252
284
|
process.emitWarning(helpMsg, {
|
|
@@ -255,6 +287,8 @@ class LightspeedSDKCore {
|
|
|
255
287
|
// Send email notification about token refresh failure
|
|
256
288
|
await sendTokenRefreshFailureEmail(error, this.accountID);
|
|
257
289
|
throw new Error(helpMsg);
|
|
290
|
+
} finally{
|
|
291
|
+
this.refreshInProgress = false; // Release the lock
|
|
258
292
|
}
|
|
259
293
|
}
|
|
260
294
|
// Core API request handler
|
|
@@ -491,6 +525,7 @@ class LightspeedSDKCore {
|
|
|
491
525
|
this.lastResponse = null;
|
|
492
526
|
this.token = null;
|
|
493
527
|
this.tokenExpiry = null;
|
|
528
|
+
this.refreshInProgress = false;
|
|
494
529
|
// Token storage interface - defaults to in-memory if not provided
|
|
495
530
|
this.tokenStorage = tokenStorage || new InMemoryTokenStorage();
|
|
496
531
|
}
|
|
@@ -144,6 +144,7 @@ export class LightspeedSDKCore {
|
|
|
144
144
|
this.lastResponse = null;
|
|
145
145
|
this.token = null;
|
|
146
146
|
this.tokenExpiry = null;
|
|
147
|
+
this.refreshInProgress = false;
|
|
147
148
|
|
|
148
149
|
// Token storage interface - defaults to in-memory if not provided
|
|
149
150
|
this.tokenStorage = tokenStorage || new InMemoryTokenStorage();
|
|
@@ -216,10 +217,11 @@ export class LightspeedSDKCore {
|
|
|
216
217
|
// Token management
|
|
217
218
|
async getToken() {
|
|
218
219
|
const now = new Date();
|
|
219
|
-
const bufferTime = 5 * 60 * 1000; // 5
|
|
220
|
+
const bufferTime = 5 * 60 * 1000; // 5-minute buffer
|
|
220
221
|
|
|
221
222
|
const storedTokens = await this.tokenStorage.getTokens();
|
|
222
223
|
|
|
224
|
+
// Check if the token is still valid
|
|
223
225
|
if (storedTokens.access_token && storedTokens.expires_at) {
|
|
224
226
|
const expiryTime = new Date(storedTokens.expires_at);
|
|
225
227
|
if (expiryTime.getTime() - now.getTime() > bufferTime) {
|
|
@@ -228,20 +230,40 @@ export class LightspeedSDKCore {
|
|
|
228
230
|
}
|
|
229
231
|
}
|
|
230
232
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
// Prevent concurrent refresh attempts
|
|
234
|
+
if (this.refreshInProgress) {
|
|
235
|
+
console.log("🔄 Token refresh already in progress. Waiting...");
|
|
236
|
+
while (this.refreshInProgress) {
|
|
237
|
+
await sleep(100); // Wait for the ongoing refresh to complete
|
|
238
|
+
}
|
|
239
|
+
// After waiting, check if the token is now valid
|
|
240
|
+
const updatedTokens = await this.tokenStorage.getTokens();
|
|
241
|
+
if (updatedTokens.access_token && updatedTokens.expires_at) {
|
|
242
|
+
const expiryTime = new Date(updatedTokens.expires_at);
|
|
243
|
+
if (expiryTime.getTime() - now.getTime() > bufferTime) {
|
|
244
|
+
this.token = updatedTokens.access_token;
|
|
245
|
+
return this.token;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// If still invalid, proceed to refresh
|
|
235
249
|
}
|
|
236
250
|
|
|
237
|
-
|
|
238
|
-
grant_type: "refresh_token",
|
|
239
|
-
client_id: this.clientID,
|
|
240
|
-
client_secret: this.clientSecret,
|
|
241
|
-
refresh_token: refreshToken,
|
|
242
|
-
};
|
|
251
|
+
this.refreshInProgress = true; // Lock the refresh process
|
|
243
252
|
|
|
244
253
|
try {
|
|
254
|
+
const refreshToken = storedTokens.refresh_token || this.refreshToken;
|
|
255
|
+
|
|
256
|
+
if (!refreshToken) {
|
|
257
|
+
throw new Error("No refresh token available");
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const body = {
|
|
261
|
+
grant_type: "refresh_token",
|
|
262
|
+
client_id: this.clientID,
|
|
263
|
+
client_secret: this.clientSecret,
|
|
264
|
+
refresh_token: refreshToken,
|
|
265
|
+
};
|
|
266
|
+
|
|
245
267
|
const response = await axios({
|
|
246
268
|
url: this.tokenUrl,
|
|
247
269
|
method: "post",
|
|
@@ -252,6 +274,7 @@ export class LightspeedSDKCore {
|
|
|
252
274
|
const tokenData = response.data;
|
|
253
275
|
const expiresAt = new Date(now.getTime() + tokenData.expires_in * 1000);
|
|
254
276
|
|
|
277
|
+
// Save the new tokens
|
|
255
278
|
await this.tokenStorage.setTokens({
|
|
256
279
|
access_token: tokenData.access_token,
|
|
257
280
|
refresh_token: tokenData.refresh_token,
|
|
@@ -262,8 +285,24 @@ export class LightspeedSDKCore {
|
|
|
262
285
|
this.token = tokenData.access_token;
|
|
263
286
|
this.tokenExpiry = expiresAt;
|
|
264
287
|
|
|
288
|
+
console.log("✅ Token refresh successful");
|
|
265
289
|
return this.token;
|
|
266
290
|
} catch (error) {
|
|
291
|
+
console.error("❌ Token refresh failed:", error.message);
|
|
292
|
+
|
|
293
|
+
// Check if the token was refreshed by another process
|
|
294
|
+
const updatedTokens = await this.tokenStorage.getTokens();
|
|
295
|
+
if (updatedTokens.access_token && updatedTokens.expires_at) {
|
|
296
|
+
const expiryTime = new Date(updatedTokens.expires_at);
|
|
297
|
+
if (expiryTime.getTime() - now.getTime() > bufferTime) {
|
|
298
|
+
console.log(
|
|
299
|
+
"🔄 Token was refreshed by another process. Skipping failure email."
|
|
300
|
+
);
|
|
301
|
+
return updatedTokens.access_token;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// If the token is still invalid, send the failure email
|
|
267
306
|
const helpMsg =
|
|
268
307
|
"\n❌ Token refresh failed. Your refresh token may be expired or revoked.\n" +
|
|
269
308
|
"👉 Please re-authenticate using the SDK's CLI or obtain a new refresh token.\n" +
|
|
@@ -276,6 +315,8 @@ export class LightspeedSDKCore {
|
|
|
276
315
|
await sendTokenRefreshFailureEmail(error, this.accountID);
|
|
277
316
|
|
|
278
317
|
throw new Error(helpMsg);
|
|
318
|
+
} finally {
|
|
319
|
+
this.refreshInProgress = false; // Release the lock
|
|
279
320
|
}
|
|
280
321
|
}
|
|
281
322
|
|