lightspeed-retail-sdk 3.4.0 → 3.4.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/README.md +90 -39
- package/dist/src/bin/cli.js +110 -12
- package/dist/src/core/LightspeedSDK.cjs +33 -2
- package/dist/src/core/LightspeedSDK.mjs +51 -2
- package/dist/src/storage/StorageConfig.mjs +175 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
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.4.
|
|
5
|
+
**Current Version: 3.4.2** — Auto-discovery system for seamless cron job and production deployment.
|
|
6
6
|
|
|
7
7
|
## **🆕 Recent Updates (v3.4.0)**
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
9
|
+
- **🤖 Auto-Discovery System**: SDK automatically discovers storage configuration after CLI setup - perfect for cron jobs and automated scripts
|
|
10
|
+
- **🔧 Enhanced Token Injection**: Interactive `inject-tokens` command with prompts for access/refresh tokens, expiry settings, and storage backend selection
|
|
11
|
+
- **🏭 Production Environment Support**: Login command supports headless environments with `--no-browser` option
|
|
12
|
+
- **📁 Storage Configuration Management**: Automatic saving and updating of storage configurations during CLI operations
|
|
13
|
+
- **🔄 Migration Transparency**: Storage migrations automatically update all scripts to use new storage
|
|
11
14
|
- **💡 Improved CLI UX**: Better error messages, token validation, and clear instructions for manual OAuth flows
|
|
12
|
-
- **📖 Enhanced Documentation**: Updated README and CLI help with production deployment guidance
|
|
13
15
|
|
|
14
16
|
## **Previous Updates (v3.3.5)**
|
|
15
17
|
|
|
@@ -27,12 +29,14 @@ A modern JavaScript SDK for interacting with the Lightspeed Retail API. This SDK
|
|
|
27
29
|
|
|
28
30
|
## 🚀 Key Features
|
|
29
31
|
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
32
|
+
- **🤖 Auto-Discovery**: Zero-configuration after CLI setup - perfect for cron jobs and production
|
|
33
|
+
- **🔄 Modern API**: Object-based parameters with full backward compatibility
|
|
34
|
+
- **🕒 Timestamp Filtering**: Get only records updated since a specific time
|
|
35
|
+
- **🛡️ Robust Error Handling**: Clean, silent error handling with consistent return types
|
|
36
|
+
- **🎯 Enhanced CLI**: Interactive setup with storage selection and headless support
|
|
37
|
+
- **🔒 Multiple Storage Options**: File, encrypted, database, and in-memory token storage
|
|
38
|
+
- **📊 Comprehensive Coverage**: 20+ API methods with consistent interfaces
|
|
39
|
+
- **⚡ Seamless Token Management**: Automatic token refresh with failure notifications
|
|
36
40
|
|
|
37
41
|
## 🔄 Migrating from 3.1.x
|
|
38
42
|
|
|
@@ -73,7 +77,8 @@ const items = await sdk.getItems({
|
|
|
73
77
|
## Table of Contents
|
|
74
78
|
|
|
75
79
|
- [Another Unofficial Lightspeed Retail V3 API SDK](#another-unofficial-lightspeed-retail-v3-api-sdk)
|
|
76
|
-
- [**🆕 Recent Updates (v3.
|
|
80
|
+
- [**🆕 Recent Updates (v3.4.0)**](#-recent-updates-v340)
|
|
81
|
+
- [**Previous Updates (v3.3.5)**](#previous-updates-v335)
|
|
77
82
|
- [🚀 Key Features](#-key-features)
|
|
78
83
|
- [🔄 Migrating from 3.1.x](#-migrating-from-31x)
|
|
79
84
|
- [Backward Compatibility](#backward-compatibility)
|
|
@@ -112,7 +117,9 @@ const items = await sdk.getItems({
|
|
|
112
117
|
- [Quick Start](#quick-start)
|
|
113
118
|
- [Modern CLI-First Approach (Recommended)](#modern-cli-first-approach-recommended)
|
|
114
119
|
- [Alternative: Local Installation](#alternative-local-installation)
|
|
115
|
-
- [
|
|
120
|
+
- [Recommended Usage (Auto-Discovery)](#recommended-usage-auto-discovery)
|
|
121
|
+
- [Explicit Storage (Advanced)](#explicit-storage-advanced)
|
|
122
|
+
- [Production \& Cron Job Setup](#production--cron-job-setup)
|
|
116
123
|
- [Manual Token Management (Advanced)](#manual-token-management-advanced)
|
|
117
124
|
- [Option 1: Interactive Token Injection (Easiest)](#option-1-interactive-token-injection-easiest)
|
|
118
125
|
- [Option 2: Programmatic Token Storage](#option-2-programmatic-token-storage)
|
|
@@ -643,50 +650,94 @@ npx lightspeed-retail-sdk login
|
|
|
643
650
|
Then in your code:
|
|
644
651
|
|
|
645
652
|
```javascript
|
|
646
|
-
import LightspeedRetailSDK
|
|
647
|
-
FileTokenStorage,
|
|
648
|
-
EncryptedTokenStorage,
|
|
649
|
-
} from "lightspeed-retail-sdk";
|
|
650
|
-
import dotenv from "dotenv";
|
|
651
|
-
dotenv.config();
|
|
652
|
-
|
|
653
|
-
// Use the same storage configuration as your CLI
|
|
654
|
-
const fileStorage = new FileTokenStorage(
|
|
655
|
-
process.env.LIGHTSPEED_TOKEN_FILE || "./tokens/encrypted-tokens.json"
|
|
656
|
-
);
|
|
657
|
-
const tokenStorage = process.env.LIGHTSPEED_ENCRYPTION_KEY
|
|
658
|
-
? new EncryptedTokenStorage(
|
|
659
|
-
fileStorage,
|
|
660
|
-
process.env.LIGHTSPEED_ENCRYPTION_KEY
|
|
661
|
-
)
|
|
662
|
-
: fileStorage;
|
|
653
|
+
import LightspeedRetailSDK from "lightspeed-retail-sdk";
|
|
663
654
|
|
|
655
|
+
// After CLI setup, your code is this simple:
|
|
664
656
|
const api = new LightspeedRetailSDK({
|
|
665
|
-
accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
|
|
666
657
|
clientID: process.env.LIGHTSPEED_CLIENT_ID,
|
|
667
658
|
clientSecret: process.env.LIGHTSPEED_CLIENT_SECRET,
|
|
668
|
-
|
|
659
|
+
accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
|
|
660
|
+
// Storage automatically discovered from CLI setup!
|
|
669
661
|
});
|
|
670
662
|
|
|
671
|
-
// The SDK
|
|
663
|
+
// The SDK automatically finds your tokens and refreshes them as needed
|
|
664
|
+
const items = await api.getItems();
|
|
672
665
|
export default api;
|
|
673
666
|
```
|
|
674
667
|
|
|
675
|
-
###
|
|
668
|
+
### Recommended Usage (Auto-Discovery)
|
|
669
|
+
|
|
670
|
+
**Best for most users**: After one-time CLI setup, no configuration needed:
|
|
676
671
|
|
|
677
672
|
```javascript
|
|
678
673
|
import LightspeedRetailSDK from "lightspeed-retail-sdk";
|
|
679
674
|
|
|
675
|
+
// After running: npm run cli login (one time setup)
|
|
680
676
|
const api = new LightspeedRetailSDK({
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
677
|
+
clientID: process.env.LIGHTSPEED_CLIENT_ID,
|
|
678
|
+
clientSecret: process.env.LIGHTSPEED_CLIENT_SECRET,
|
|
679
|
+
accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
|
|
680
|
+
// No tokenStorage needed - automatically discovered!
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
// Works immediately, handles token refresh automatically
|
|
684
|
+
const items = await api.getItems();
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### Explicit Storage (Advanced)
|
|
688
|
+
|
|
689
|
+
For advanced use cases where you want to specify storage manually:
|
|
690
|
+
|
|
691
|
+
```javascript
|
|
692
|
+
import LightspeedRetailSDK, {
|
|
693
|
+
FileTokenStorage,
|
|
694
|
+
EncryptedTokenStorage
|
|
695
|
+
} from "lightspeed-retail-sdk";
|
|
696
|
+
|
|
697
|
+
const fileStorage = new FileTokenStorage('./lightspeed-tokens.json');
|
|
698
|
+
const tokenStorage = process.env.LIGHTSPEED_ENCRYPTION_KEY
|
|
699
|
+
? new EncryptedTokenStorage(fileStorage, process.env.LIGHTSPEED_ENCRYPTION_KEY)
|
|
700
|
+
: fileStorage;
|
|
701
|
+
|
|
702
|
+
const api = new LightspeedRetailSDK({
|
|
703
|
+
clientID: process.env.LIGHTSPEED_CLIENT_ID,
|
|
704
|
+
clientSecret: process.env.LIGHTSPEED_CLIENT_SECRET,
|
|
705
|
+
accountID: process.env.LIGHTSPEED_ACCOUNT_ID,
|
|
706
|
+
tokenStorage: tokenStorage, // Explicit storage choice
|
|
686
707
|
});
|
|
687
708
|
```
|
|
688
709
|
|
|
689
|
-
|
|
710
|
+
✅ **Auto-Discovery**: The SDK automatically discovers your storage configuration after initial CLI setup. Perfect for cron jobs and automated scripts.
|
|
711
|
+
|
|
712
|
+
✅ **Explicit Storage**: You can still explicitly provide tokenStorage for advanced use cases. Choose from FileTokenStorage, EncryptedTokenStorage (recommended), DatabaseTokenStorage, or InMemoryTokenStorage (not recommended).
|
|
713
|
+
|
|
714
|
+
### Production & Cron Job Setup
|
|
715
|
+
|
|
716
|
+
The auto-discovery system is perfect for production environments and automated scripts:
|
|
717
|
+
|
|
718
|
+
1. **Initial Setup** (run once):
|
|
719
|
+
|
|
720
|
+
```bash
|
|
721
|
+
# Interactive setup with storage selection
|
|
722
|
+
npm run cli login
|
|
723
|
+
|
|
724
|
+
# Or for headless environments:
|
|
725
|
+
npm run cli login --no-browser
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
2. **Your Scripts Work Automatically**:
|
|
729
|
+
- Cron jobs find storage configuration automatically
|
|
730
|
+
- Token refresh happens seamlessly in background
|
|
731
|
+
- Storage migrations update all scripts automatically
|
|
732
|
+
- No configuration management needed
|
|
733
|
+
|
|
734
|
+
3. **Environment Variables** (recommended):
|
|
735
|
+
|
|
736
|
+
```bash
|
|
737
|
+
export LIGHTSPEED_CLIENT_ID="your-client-id"
|
|
738
|
+
export LIGHTSPEED_CLIENT_SECRET="your-client-secret"
|
|
739
|
+
export LIGHTSPEED_ACCOUNT_ID="your-account-id"
|
|
740
|
+
```
|
|
690
741
|
|
|
691
742
|
### Manual Token Management (Advanced)
|
|
692
743
|
|
package/dist/src/bin/cli.js
CHANGED
|
@@ -5,7 +5,9 @@ import dotenv from "dotenv";
|
|
|
5
5
|
import {
|
|
6
6
|
FileTokenStorage,
|
|
7
7
|
EncryptedTokenStorage,
|
|
8
|
+
DatabaseTokenStorage,
|
|
8
9
|
} from "../storage/TokenStorage.mjs";
|
|
10
|
+
import { StorageConfig } from "../storage/StorageConfig.mjs";
|
|
9
11
|
import LightspeedRetailSDK from "../../index.mjs";
|
|
10
12
|
import inquirer from "inquirer";
|
|
11
13
|
import { createInterface } from "readline";
|
|
@@ -197,7 +199,7 @@ program
|
|
|
197
199
|
).toISOString();
|
|
198
200
|
|
|
199
201
|
// 5. Store tokens using storage backend
|
|
200
|
-
storageBackend = await selectStorageBackend();
|
|
202
|
+
storageBackend = await selectStorageBackend(); // Save config for initial setup
|
|
201
203
|
await storageBackend.setTokens({
|
|
202
204
|
access_token: tokenData.access_token,
|
|
203
205
|
refresh_token: tokenData.refresh_token,
|
|
@@ -228,7 +230,7 @@ program
|
|
|
228
230
|
.action(async () => {
|
|
229
231
|
let storageBackend = null;
|
|
230
232
|
try {
|
|
231
|
-
storageBackend = await selectStorageBackend();
|
|
233
|
+
storageBackend = await selectStorageBackend(false); // Read-only, don't save config
|
|
232
234
|
const tokens = await storageBackend.getTokens();
|
|
233
235
|
if (!tokens || !tokens.access_token) {
|
|
234
236
|
console.log("\n⚠️ No tokens found in storage.");
|
|
@@ -328,6 +330,10 @@ program
|
|
|
328
330
|
err.message
|
|
329
331
|
);
|
|
330
332
|
}
|
|
333
|
+
// Remove storage config file
|
|
334
|
+
const storageConfig = new StorageConfig();
|
|
335
|
+
await storageConfig.removeConfig();
|
|
336
|
+
console.log("🗑️ Storage configuration cleared");
|
|
331
337
|
return;
|
|
332
338
|
}
|
|
333
339
|
case "postgres": {
|
|
@@ -363,6 +369,10 @@ program
|
|
|
363
369
|
err.message
|
|
364
370
|
);
|
|
365
371
|
}
|
|
372
|
+
// Remove storage config file
|
|
373
|
+
const storageConfig = new StorageConfig();
|
|
374
|
+
await storageConfig.removeConfig();
|
|
375
|
+
console.log("🗑️ Storage configuration cleared");
|
|
366
376
|
return;
|
|
367
377
|
}
|
|
368
378
|
case "mongodb": {
|
|
@@ -400,6 +410,10 @@ program
|
|
|
400
410
|
err.message
|
|
401
411
|
);
|
|
402
412
|
}
|
|
413
|
+
// Remove storage config file
|
|
414
|
+
const storageConfig = new StorageConfig();
|
|
415
|
+
await storageConfig.removeConfig();
|
|
416
|
+
console.log("🗑️ Storage configuration cleared");
|
|
403
417
|
return;
|
|
404
418
|
}
|
|
405
419
|
default:
|
|
@@ -437,6 +451,11 @@ program
|
|
|
437
451
|
await fileStorage.setTokens({});
|
|
438
452
|
console.log(`\n✅ Token file cleared: ${tokenFile}`);
|
|
439
453
|
}
|
|
454
|
+
|
|
455
|
+
// Remove storage config file
|
|
456
|
+
const storageConfig = new StorageConfig();
|
|
457
|
+
await storageConfig.removeConfig();
|
|
458
|
+
console.log("🗑️ Storage configuration cleared");
|
|
440
459
|
});
|
|
441
460
|
|
|
442
461
|
program
|
|
@@ -550,7 +569,7 @@ program
|
|
|
550
569
|
}
|
|
551
570
|
|
|
552
571
|
console.log("\n📁 Token Storage Configuration");
|
|
553
|
-
storageBackend = await selectStorageBackend();
|
|
572
|
+
storageBackend = await selectStorageBackend(); // Save config for inject-tokens setup
|
|
554
573
|
|
|
555
574
|
// Validate tokens format (basic check)
|
|
556
575
|
if (accessToken.length < 10) {
|
|
@@ -599,7 +618,7 @@ program
|
|
|
599
618
|
console.log("\n🔄 Token Storage Migration Wizard\n");
|
|
600
619
|
// Prompt for source backend
|
|
601
620
|
console.log("Select SOURCE storage backend:");
|
|
602
|
-
sourceBackend = await selectStorageBackend();
|
|
621
|
+
sourceBackend = await selectStorageBackend(false); // Don't save config for source
|
|
603
622
|
const sourceTokens = await sourceBackend.getTokens();
|
|
604
623
|
if (!sourceTokens || !sourceTokens.access_token) {
|
|
605
624
|
console.error("\n❌ No tokens found in source storage. Aborting.");
|
|
@@ -610,7 +629,7 @@ program
|
|
|
610
629
|
|
|
611
630
|
// Prompt for destination backend
|
|
612
631
|
console.log("\nSelect DESTINATION storage backend:");
|
|
613
|
-
destBackend = await selectStorageBackend();
|
|
632
|
+
destBackend = await selectStorageBackend(false); // Don't save config yet - will save after successful migration
|
|
614
633
|
|
|
615
634
|
// Attempt to create table/collection/file if it doesn't exist (best effort)
|
|
616
635
|
switch (destBackend.constructor.name) {
|
|
@@ -726,6 +745,16 @@ program
|
|
|
726
745
|
}
|
|
727
746
|
await destBackend.setTokens(sourceTokens);
|
|
728
747
|
console.log("\n🎉 Tokens migrated successfully!");
|
|
748
|
+
|
|
749
|
+
// Update storage configuration to point to the new destination
|
|
750
|
+
try {
|
|
751
|
+
const storageConfig = new StorageConfig();
|
|
752
|
+
const config = await getStorageConfig(destBackend);
|
|
753
|
+
await storageConfig.saveConfig(config);
|
|
754
|
+
console.log("✅ Storage configuration updated for auto-discovery");
|
|
755
|
+
} catch (configError) {
|
|
756
|
+
console.warn(`Warning: Could not update storage config: ${configError.message}`);
|
|
757
|
+
}
|
|
729
758
|
} catch (error) {
|
|
730
759
|
console.error("\n❌ Migration failed:", error.message);
|
|
731
760
|
} finally {
|
|
@@ -741,7 +770,7 @@ program
|
|
|
741
770
|
.action(async () => {
|
|
742
771
|
let storageBackend = null;
|
|
743
772
|
try {
|
|
744
|
-
storageBackend = await selectStorageBackend();
|
|
773
|
+
storageBackend = await selectStorageBackend(false); // Read-only, don't save config
|
|
745
774
|
const tokens = await storageBackend.getTokens();
|
|
746
775
|
if (!tokens || !tokens.access_token) {
|
|
747
776
|
console.log("\n⚠️ No tokens found in storage. Please login first.");
|
|
@@ -1045,7 +1074,80 @@ async function sendTestTokenRefreshFailureEmail(error, accountID) {
|
|
|
1045
1074
|
}
|
|
1046
1075
|
}
|
|
1047
1076
|
|
|
1048
|
-
|
|
1077
|
+
/**
|
|
1078
|
+
* Extract configuration from a storage instance for saving
|
|
1079
|
+
* @param {TokenStorage} storage - Storage instance
|
|
1080
|
+
* @returns {Object} Configuration object
|
|
1081
|
+
*/
|
|
1082
|
+
async function getStorageConfig(storage) {
|
|
1083
|
+
// Handle EncryptedTokenStorage wrapper
|
|
1084
|
+
if (storage.adapter) {
|
|
1085
|
+
const innerConfig = await getStorageConfig(storage.adapter);
|
|
1086
|
+
return {
|
|
1087
|
+
type: innerConfig.type === "file" ? "encrypted-file" : "database",
|
|
1088
|
+
settings: {
|
|
1089
|
+
...innerConfig.settings,
|
|
1090
|
+
encrypted: true,
|
|
1091
|
+
encryptionKey: process.env.LIGHTSPEED_ENCRYPTION_KEY || "[provided]"
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Handle FileTokenStorage
|
|
1097
|
+
if (storage.filePath) {
|
|
1098
|
+
return {
|
|
1099
|
+
type: "file",
|
|
1100
|
+
settings: {
|
|
1101
|
+
filePath: storage.filePath
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
|
|
1106
|
+
// Handle DatabaseTokenStorage
|
|
1107
|
+
if (storage.dbConnectionString) {
|
|
1108
|
+
return {
|
|
1109
|
+
type: "database",
|
|
1110
|
+
settings: {
|
|
1111
|
+
connectionString: storage.dbConnectionString,
|
|
1112
|
+
dbType: storage.dbType,
|
|
1113
|
+
tableName: storage.tableName,
|
|
1114
|
+
appId: storage.appId
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
throw new Error("Unknown storage type for configuration");
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
async function selectStorageBackend(saveConfig = true) {
|
|
1123
|
+
const storageConfig = new StorageConfig();
|
|
1124
|
+
|
|
1125
|
+
// Try auto-discovery first if not saving config (read-only operations)
|
|
1126
|
+
if (!saveConfig) {
|
|
1127
|
+
const { autoDiscoverStorage } = await import("../storage/StorageConfig.mjs");
|
|
1128
|
+
const autoStorage = await autoDiscoverStorage();
|
|
1129
|
+
if (autoStorage) {
|
|
1130
|
+
return autoStorage;
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const storage = await selectStorageBackendInternal();
|
|
1135
|
+
|
|
1136
|
+
// Save configuration for auto-discovery if requested
|
|
1137
|
+
if (saveConfig) {
|
|
1138
|
+
try {
|
|
1139
|
+
const config = await getStorageConfig(storage);
|
|
1140
|
+
await storageConfig.saveConfig(config);
|
|
1141
|
+
console.log("✅ Storage configuration saved for auto-discovery");
|
|
1142
|
+
} catch (error) {
|
|
1143
|
+
console.warn(`Warning: Could not save storage config: ${error.message}`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
return storage;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
async function selectStorageBackendInternal() {
|
|
1049
1151
|
const { storageType } = await inquirer.prompt([
|
|
1050
1152
|
{
|
|
1051
1153
|
type: "list",
|
|
@@ -1148,10 +1250,6 @@ async function selectStorageBackend() {
|
|
|
1148
1250
|
default:
|
|
1149
1251
|
throw new Error("Unsupported database type");
|
|
1150
1252
|
}
|
|
1151
|
-
// Dynamically import DatabaseTokenStorage
|
|
1152
|
-
const { DatabaseTokenStorage } = await import(
|
|
1153
|
-
"../storage/TokenStorage.mjs"
|
|
1154
|
-
);
|
|
1155
1253
|
const dbStorage = new DatabaseTokenStorage(dbConnectionString, {
|
|
1156
1254
|
dbType,
|
|
1157
1255
|
tableName,
|
|
@@ -1239,7 +1337,7 @@ program
|
|
|
1239
1337
|
console.log("🔄 Refreshing stored access token...\n");
|
|
1240
1338
|
|
|
1241
1339
|
// Get storage backend
|
|
1242
|
-
storageBackend = await selectStorageBackend();
|
|
1340
|
+
storageBackend = await selectStorageBackend(false); // Read-only, don't save config
|
|
1243
1341
|
|
|
1244
1342
|
// Get existing tokens
|
|
1245
1343
|
const tokens = await storageBackend.getTokens();
|
|
@@ -187,6 +187,27 @@ async function sendTokenRefreshFailureEmail(error, accountID) {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
class LightspeedSDKCore {
|
|
190
|
+
/**
|
|
191
|
+
* Initialize token storage through auto-discovery if not explicitly provided
|
|
192
|
+
* @private
|
|
193
|
+
*/ async _initializeStorage() {
|
|
194
|
+
if (this._storageInitialized) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!this.tokenStorage) {
|
|
198
|
+
try {
|
|
199
|
+
const { autoDiscoverStorage } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../storage/StorageConfig.cjs")));
|
|
200
|
+
this.tokenStorage = await autoDiscoverStorage();
|
|
201
|
+
if (!this.tokenStorage) {
|
|
202
|
+
throw new Error("No token storage found. Please either:\n" + "• Run the CLI to set up storage: npm run cli login\n" + "• Or explicitly provide tokenStorage:\n" + " - FileTokenStorage('./tokens.json')\n" + " - EncryptedTokenStorage(fileStorage, key) [recommended]\n" + " - DatabaseTokenStorage(connection, options)\n" + " - InMemoryTokenStorage() [NOT RECOMMENDED]\n\n" + "See documentation: https://github.com/darrylmorley/lightspeed-retail-sdk#token-storage");
|
|
203
|
+
}
|
|
204
|
+
} catch (error) {
|
|
205
|
+
// If auto-discovery fails, throw helpful error
|
|
206
|
+
throw new Error("Failed to auto-discover token storage. " + error.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
this._storageInitialized = true;
|
|
210
|
+
}
|
|
190
211
|
// Core error handling
|
|
191
212
|
handleError(context, err, shouldThrow = true) {
|
|
192
213
|
const errorMessage = (err === null || err === void 0 ? void 0 : err.message) || "Unknown error occurred";
|
|
@@ -206,6 +227,8 @@ class LightspeedSDKCore {
|
|
|
206
227
|
}
|
|
207
228
|
// Token management
|
|
208
229
|
async getToken() {
|
|
230
|
+
// Ensure storage is initialized
|
|
231
|
+
await this._initializeStorage();
|
|
209
232
|
const now = new Date();
|
|
210
233
|
const bufferTime = 5 * 60 * 1000; // 5-minute buffer
|
|
211
234
|
const storedTokens = await this.tokenStorage.getTokens();
|
|
@@ -479,6 +502,8 @@ class LightspeedSDKCore {
|
|
|
479
502
|
}
|
|
480
503
|
}
|
|
481
504
|
async getTokenInfo() {
|
|
505
|
+
// Ensure storage is initialized
|
|
506
|
+
await this._initializeStorage();
|
|
482
507
|
const storedTokens = await this.tokenStorage.getTokens();
|
|
483
508
|
return {
|
|
484
509
|
hasAccessToken: !!storedTokens.access_token,
|
|
@@ -526,8 +551,14 @@ class LightspeedSDKCore {
|
|
|
526
551
|
this.token = null;
|
|
527
552
|
this.tokenExpiry = null;
|
|
528
553
|
this.refreshInProgress = false;
|
|
529
|
-
// Token storage interface -
|
|
530
|
-
|
|
554
|
+
// Token storage interface - can be explicitly provided or auto-discovered
|
|
555
|
+
if (tokenStorage) {
|
|
556
|
+
this.tokenStorage = tokenStorage;
|
|
557
|
+
} else {
|
|
558
|
+
// Auto-discovery will be handled by async initialization
|
|
559
|
+
this.tokenStorage = null;
|
|
560
|
+
this._storageInitialized = false;
|
|
561
|
+
}
|
|
531
562
|
}
|
|
532
563
|
}
|
|
533
564
|
_define_property(LightspeedSDKCore, "BASE_URL", "https://api.lightspeedapp.com/API/V3/Account");
|
|
@@ -146,8 +146,51 @@ export class LightspeedSDKCore {
|
|
|
146
146
|
this.tokenExpiry = null;
|
|
147
147
|
this.refreshInProgress = false;
|
|
148
148
|
|
|
149
|
-
// Token storage interface -
|
|
150
|
-
|
|
149
|
+
// Token storage interface - can be explicitly provided or auto-discovered
|
|
150
|
+
if (tokenStorage) {
|
|
151
|
+
this.tokenStorage = tokenStorage;
|
|
152
|
+
} else {
|
|
153
|
+
// Auto-discovery will be handled by async initialization
|
|
154
|
+
this.tokenStorage = null;
|
|
155
|
+
this._storageInitialized = false;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Initialize token storage through auto-discovery if not explicitly provided
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
async _initializeStorage() {
|
|
164
|
+
if (this._storageInitialized) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!this.tokenStorage) {
|
|
169
|
+
try {
|
|
170
|
+
const { autoDiscoverStorage } = await import("../storage/StorageConfig.mjs");
|
|
171
|
+
this.tokenStorage = await autoDiscoverStorage();
|
|
172
|
+
|
|
173
|
+
if (!this.tokenStorage) {
|
|
174
|
+
throw new Error(
|
|
175
|
+
"No token storage found. Please either:\n" +
|
|
176
|
+
"• Run the CLI to set up storage: npm run cli login\n" +
|
|
177
|
+
"• Or explicitly provide tokenStorage:\n" +
|
|
178
|
+
" - FileTokenStorage('./tokens.json')\n" +
|
|
179
|
+
" - EncryptedTokenStorage(fileStorage, key) [recommended]\n" +
|
|
180
|
+
" - DatabaseTokenStorage(connection, options)\n" +
|
|
181
|
+
" - InMemoryTokenStorage() [NOT RECOMMENDED]\n\n" +
|
|
182
|
+
"See documentation: https://github.com/darrylmorley/lightspeed-retail-sdk#token-storage"
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// If auto-discovery fails, throw helpful error
|
|
187
|
+
throw new Error(
|
|
188
|
+
"Failed to auto-discover token storage. " + error.message
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this._storageInitialized = true;
|
|
151
194
|
}
|
|
152
195
|
|
|
153
196
|
// Core error handling
|
|
@@ -216,6 +259,9 @@ export class LightspeedSDKCore {
|
|
|
216
259
|
|
|
217
260
|
// Token management
|
|
218
261
|
async getToken() {
|
|
262
|
+
// Ensure storage is initialized
|
|
263
|
+
await this._initializeStorage();
|
|
264
|
+
|
|
219
265
|
const now = new Date();
|
|
220
266
|
const bufferTime = 5 * 60 * 1000; // 5-minute buffer
|
|
221
267
|
|
|
@@ -536,6 +582,9 @@ export class LightspeedSDKCore {
|
|
|
536
582
|
}
|
|
537
583
|
|
|
538
584
|
async getTokenInfo() {
|
|
585
|
+
// Ensure storage is initialized
|
|
586
|
+
await this._initializeStorage();
|
|
587
|
+
|
|
539
588
|
const storedTokens = await this.tokenStorage.getTokens();
|
|
540
589
|
return {
|
|
541
590
|
hasAccessToken: !!storedTokens.access_token,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Storage configuration manager that tracks which storage type and settings
|
|
6
|
+
* are being used, enabling automatic storage discovery.
|
|
7
|
+
*/
|
|
8
|
+
export class StorageConfig {
|
|
9
|
+
constructor(configPath = ".lightspeed-storage-config.json") {
|
|
10
|
+
this.configPath = path.resolve(configPath);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Save storage configuration for future auto-discovery
|
|
15
|
+
* @param {Object} config - Storage configuration
|
|
16
|
+
* @param {string} config.type - Storage type: 'file', 'encrypted-file', 'database'
|
|
17
|
+
* @param {Object} config.settings - Storage-specific settings
|
|
18
|
+
*/
|
|
19
|
+
async saveConfig(config) {
|
|
20
|
+
try {
|
|
21
|
+
const configData = {
|
|
22
|
+
type: config.type,
|
|
23
|
+
settings: config.settings,
|
|
24
|
+
lastUpdated: new Date().toISOString(),
|
|
25
|
+
version: "1.0"
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
await fs.writeFile(
|
|
29
|
+
this.configPath,
|
|
30
|
+
JSON.stringify(configData, null, 2),
|
|
31
|
+
"utf8"
|
|
32
|
+
);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
console.warn(`Warning: Could not save storage config: ${error.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load existing storage configuration
|
|
40
|
+
* @returns {Object|null} Configuration object or null if not found
|
|
41
|
+
*/
|
|
42
|
+
async loadConfig() {
|
|
43
|
+
try {
|
|
44
|
+
const data = await fs.readFile(this.configPath, "utf8");
|
|
45
|
+
return JSON.parse(data);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error.code === "ENOENT") {
|
|
48
|
+
return null; // Config file doesn't exist
|
|
49
|
+
}
|
|
50
|
+
console.warn(`Warning: Could not load storage config: ${error.message}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if storage configuration exists
|
|
57
|
+
* @returns {boolean}
|
|
58
|
+
*/
|
|
59
|
+
async hasConfig() {
|
|
60
|
+
try {
|
|
61
|
+
await fs.access(this.configPath);
|
|
62
|
+
return true;
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Remove storage configuration
|
|
70
|
+
*/
|
|
71
|
+
async removeConfig() {
|
|
72
|
+
try {
|
|
73
|
+
await fs.unlink(this.configPath);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error.code !== "ENOENT") {
|
|
76
|
+
console.warn(`Warning: Could not remove storage config: ${error.message}`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a token storage instance based on saved configuration
|
|
84
|
+
* @param {Object} config - Storage configuration from StorageConfig
|
|
85
|
+
* @returns {TokenStorage} Configured storage instance
|
|
86
|
+
*/
|
|
87
|
+
export async function createStorageFromConfig(config) {
|
|
88
|
+
const {
|
|
89
|
+
FileTokenStorage,
|
|
90
|
+
EncryptedTokenStorage,
|
|
91
|
+
DatabaseTokenStorage
|
|
92
|
+
} = await import("./TokenStorage.mjs");
|
|
93
|
+
|
|
94
|
+
switch (config.type) {
|
|
95
|
+
case "file": {
|
|
96
|
+
return new FileTokenStorage(config.settings.filePath);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
case "encrypted-file": {
|
|
100
|
+
const fileStorage = new FileTokenStorage(config.settings.filePath);
|
|
101
|
+
return new EncryptedTokenStorage(fileStorage, config.settings.encryptionKey);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
case "database": {
|
|
105
|
+
const dbStorage = new DatabaseTokenStorage(
|
|
106
|
+
config.settings.connectionString,
|
|
107
|
+
{
|
|
108
|
+
dbType: config.settings.dbType,
|
|
109
|
+
tableName: config.settings.tableName,
|
|
110
|
+
appId: config.settings.appId
|
|
111
|
+
}
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (config.settings.encrypted) {
|
|
115
|
+
return new EncryptedTokenStorage(dbStorage, config.settings.encryptionKey);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return dbStorage;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
default:
|
|
122
|
+
throw new Error(`Unknown storage type: ${config.type}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Auto-discover and create the appropriate token storage based on
|
|
128
|
+
* previously saved configuration, environment variables, or defaults.
|
|
129
|
+
* @param {string} configPath - Optional path to config file
|
|
130
|
+
* @returns {TokenStorage|null} Storage instance or null if none found
|
|
131
|
+
*/
|
|
132
|
+
export async function autoDiscoverStorage(configPath) {
|
|
133
|
+
const storageConfig = new StorageConfig(configPath);
|
|
134
|
+
|
|
135
|
+
// Try to load saved configuration first
|
|
136
|
+
const config = await storageConfig.loadConfig();
|
|
137
|
+
if (config) {
|
|
138
|
+
try {
|
|
139
|
+
return await createStorageFromConfig(config);
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.warn(`Warning: Could not create storage from saved config: ${error.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fallback: Check for environment variables for common setups
|
|
146
|
+
if (process.env.LIGHTSPEED_TOKEN_FILE) {
|
|
147
|
+
const { FileTokenStorage, EncryptedTokenStorage } = await import("./TokenStorage.mjs");
|
|
148
|
+
const fileStorage = new FileTokenStorage(process.env.LIGHTSPEED_TOKEN_FILE);
|
|
149
|
+
|
|
150
|
+
if (process.env.LIGHTSPEED_ENCRYPTION_KEY) {
|
|
151
|
+
return new EncryptedTokenStorage(fileStorage, process.env.LIGHTSPEED_ENCRYPTION_KEY);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return fileStorage;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check for default token file locations
|
|
158
|
+
const defaultPaths = [
|
|
159
|
+
".lightspeed-tokens.json",
|
|
160
|
+
"./tokens/encrypted-tokens.json",
|
|
161
|
+
"./lightspeed-tokens.json"
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
for (const defaultPath of defaultPaths) {
|
|
165
|
+
try {
|
|
166
|
+
await fs.access(defaultPath);
|
|
167
|
+
const { FileTokenStorage } = await import("./TokenStorage.mjs");
|
|
168
|
+
return new FileTokenStorage(defaultPath);
|
|
169
|
+
} catch {
|
|
170
|
+
// File doesn't exist, continue to next
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return null;
|
|
175
|
+
}
|