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 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.3** — increase token buffer time from 1 minute to 5 minutes for improved token management
5
+ **Current Version: 3.3.5** — improve error checking on token refresh to prevent false email warnings.
6
6
 
7
- ## **🆕 Recent Updates (v3.3.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.3)**](#-recent-updates-v333)
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 minute buffer
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
- const refreshToken = storedTokens.refresh_token || this.refreshToken;
220
- if (!refreshToken) {
221
- throw new Error("No refresh token available");
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
- const body = {
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 minute buffer
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
- const refreshToken = storedTokens.refresh_token || this.refreshToken;
232
-
233
- if (!refreshToken) {
234
- throw new Error("No refresh token available");
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
- const body = {
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lightspeed-retail-sdk",
3
- "version": "3.3.3",
3
+ "version": "3.3.5",
4
4
  "description": "Another unofficial Lightspeed Retail API SDK for Node.js",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",