lightspeed-retail-sdk 3.4.0 → 3.4.1
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 +83 -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.1** — 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.");
|
|
@@ -550,7 +552,7 @@ program
|
|
|
550
552
|
}
|
|
551
553
|
|
|
552
554
|
console.log("\n📁 Token Storage Configuration");
|
|
553
|
-
storageBackend = await selectStorageBackend();
|
|
555
|
+
storageBackend = await selectStorageBackend(); // Save config for inject-tokens setup
|
|
554
556
|
|
|
555
557
|
// Validate tokens format (basic check)
|
|
556
558
|
if (accessToken.length < 10) {
|
|
@@ -599,7 +601,7 @@ program
|
|
|
599
601
|
console.log("\n🔄 Token Storage Migration Wizard\n");
|
|
600
602
|
// Prompt for source backend
|
|
601
603
|
console.log("Select SOURCE storage backend:");
|
|
602
|
-
sourceBackend = await selectStorageBackend();
|
|
604
|
+
sourceBackend = await selectStorageBackend(false); // Don't save config for source
|
|
603
605
|
const sourceTokens = await sourceBackend.getTokens();
|
|
604
606
|
if (!sourceTokens || !sourceTokens.access_token) {
|
|
605
607
|
console.error("\n❌ No tokens found in source storage. Aborting.");
|
|
@@ -610,7 +612,7 @@ program
|
|
|
610
612
|
|
|
611
613
|
// Prompt for destination backend
|
|
612
614
|
console.log("\nSelect DESTINATION storage backend:");
|
|
613
|
-
destBackend = await selectStorageBackend();
|
|
615
|
+
destBackend = await selectStorageBackend(false); // Don't save config yet - will save after successful migration
|
|
614
616
|
|
|
615
617
|
// Attempt to create table/collection/file if it doesn't exist (best effort)
|
|
616
618
|
switch (destBackend.constructor.name) {
|
|
@@ -726,6 +728,16 @@ program
|
|
|
726
728
|
}
|
|
727
729
|
await destBackend.setTokens(sourceTokens);
|
|
728
730
|
console.log("\n🎉 Tokens migrated successfully!");
|
|
731
|
+
|
|
732
|
+
// Update storage configuration to point to the new destination
|
|
733
|
+
try {
|
|
734
|
+
const storageConfig = new StorageConfig();
|
|
735
|
+
const config = await getStorageConfig(destBackend);
|
|
736
|
+
await storageConfig.saveConfig(config);
|
|
737
|
+
console.log("✅ Storage configuration updated for auto-discovery");
|
|
738
|
+
} catch (configError) {
|
|
739
|
+
console.warn(`Warning: Could not update storage config: ${configError.message}`);
|
|
740
|
+
}
|
|
729
741
|
} catch (error) {
|
|
730
742
|
console.error("\n❌ Migration failed:", error.message);
|
|
731
743
|
} finally {
|
|
@@ -741,7 +753,7 @@ program
|
|
|
741
753
|
.action(async () => {
|
|
742
754
|
let storageBackend = null;
|
|
743
755
|
try {
|
|
744
|
-
storageBackend = await selectStorageBackend();
|
|
756
|
+
storageBackend = await selectStorageBackend(false); // Read-only, don't save config
|
|
745
757
|
const tokens = await storageBackend.getTokens();
|
|
746
758
|
if (!tokens || !tokens.access_token) {
|
|
747
759
|
console.log("\n⚠️ No tokens found in storage. Please login first.");
|
|
@@ -1045,7 +1057,70 @@ async function sendTestTokenRefreshFailureEmail(error, accountID) {
|
|
|
1045
1057
|
}
|
|
1046
1058
|
}
|
|
1047
1059
|
|
|
1048
|
-
|
|
1060
|
+
/**
|
|
1061
|
+
* Extract configuration from a storage instance for saving
|
|
1062
|
+
* @param {TokenStorage} storage - Storage instance
|
|
1063
|
+
* @returns {Object} Configuration object
|
|
1064
|
+
*/
|
|
1065
|
+
async function getStorageConfig(storage) {
|
|
1066
|
+
// Handle EncryptedTokenStorage wrapper
|
|
1067
|
+
if (storage.adapter) {
|
|
1068
|
+
const innerConfig = await getStorageConfig(storage.adapter);
|
|
1069
|
+
return {
|
|
1070
|
+
type: innerConfig.type === "file" ? "encrypted-file" : "database",
|
|
1071
|
+
settings: {
|
|
1072
|
+
...innerConfig.settings,
|
|
1073
|
+
encrypted: true,
|
|
1074
|
+
encryptionKey: process.env.LIGHTSPEED_ENCRYPTION_KEY || "[provided]"
|
|
1075
|
+
}
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Handle FileTokenStorage
|
|
1080
|
+
if (storage.filePath) {
|
|
1081
|
+
return {
|
|
1082
|
+
type: "file",
|
|
1083
|
+
settings: {
|
|
1084
|
+
filePath: storage.filePath
|
|
1085
|
+
}
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Handle DatabaseTokenStorage
|
|
1090
|
+
if (storage.dbConnectionString) {
|
|
1091
|
+
return {
|
|
1092
|
+
type: "database",
|
|
1093
|
+
settings: {
|
|
1094
|
+
connectionString: storage.dbConnectionString,
|
|
1095
|
+
dbType: storage.dbType,
|
|
1096
|
+
tableName: storage.tableName,
|
|
1097
|
+
appId: storage.appId
|
|
1098
|
+
}
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
throw new Error("Unknown storage type for configuration");
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
async function selectStorageBackend(saveConfig = true) {
|
|
1106
|
+
const storageConfig = new StorageConfig();
|
|
1107
|
+
const storage = await selectStorageBackendInternal();
|
|
1108
|
+
|
|
1109
|
+
// Save configuration for auto-discovery if requested
|
|
1110
|
+
if (saveConfig) {
|
|
1111
|
+
try {
|
|
1112
|
+
const config = await getStorageConfig(storage);
|
|
1113
|
+
await storageConfig.saveConfig(config);
|
|
1114
|
+
console.log("✅ Storage configuration saved for auto-discovery");
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
console.warn(`Warning: Could not save storage config: ${error.message}`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
return storage;
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
async function selectStorageBackendInternal() {
|
|
1049
1124
|
const { storageType } = await inquirer.prompt([
|
|
1050
1125
|
{
|
|
1051
1126
|
type: "list",
|
|
@@ -1148,10 +1223,6 @@ async function selectStorageBackend() {
|
|
|
1148
1223
|
default:
|
|
1149
1224
|
throw new Error("Unsupported database type");
|
|
1150
1225
|
}
|
|
1151
|
-
// Dynamically import DatabaseTokenStorage
|
|
1152
|
-
const { DatabaseTokenStorage } = await import(
|
|
1153
|
-
"../storage/TokenStorage.mjs"
|
|
1154
|
-
);
|
|
1155
1226
|
const dbStorage = new DatabaseTokenStorage(dbConnectionString, {
|
|
1156
1227
|
dbType,
|
|
1157
1228
|
tableName,
|
|
@@ -1239,7 +1310,7 @@ program
|
|
|
1239
1310
|
console.log("🔄 Refreshing stored access token...\n");
|
|
1240
1311
|
|
|
1241
1312
|
// Get storage backend
|
|
1242
|
-
storageBackend = await selectStorageBackend();
|
|
1313
|
+
storageBackend = await selectStorageBackend(false); // Read-only, don't save config
|
|
1243
1314
|
|
|
1244
1315
|
// Get existing tokens
|
|
1245
1316
|
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
|
+
}
|