lightspeed-retail-sdk 1.0.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.
Files changed (3) hide show
  1. package/README.md +19 -0
  2. package/index.js +340 -0
  3. package/package.json +15 -0
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Another Unofficial Lightspeed Retail V3 API SDK for Node.
2
+
3
+ ## Work In Progress
4
+
5
+ First, import LightSpeed Retail SDK:- import LightspeedRetailSDK from "./index.js";
6
+
7
+ Then intialise a new instance of LightspeedRetailSDK:-
8
+
9
+ ```
10
+ const api = new LightspeedRetailSDK({
11
+ accountID: "Your Account No.",
12
+ clientID: "Your client ID.",
13
+ clientSecret: "Your client secret.",
14
+ refreshToken: "Your refresh token.",
15
+ });
16
+
17
+ const item = await api.getItem(7947, '["Category", "Images"]');
18
+ console.log(item);
19
+ ```
package/index.js ADDED
@@ -0,0 +1,340 @@
1
+ import axios from "axios";
2
+
3
+ // Cost per operation
4
+ const getRequestUnits = (operation) => {
5
+ switch (operation) {
6
+ case "GET":
7
+ return 1;
8
+ case "POST":
9
+ return 10;
10
+ case "PUT":
11
+ return 10;
12
+ default:
13
+ return 10;
14
+ }
15
+ };
16
+
17
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
18
+
19
+ class LightspeedRetailSDK {
20
+ constructor(opts) {
21
+ const { clientID, clientSecret, refreshToken, accountID } = opts;
22
+
23
+ this.clientID = clientID;
24
+ this.clientSecret = clientSecret;
25
+ this.refreshToken = refreshToken;
26
+ this.accountID = accountID;
27
+ this.baseUrl = "https://api.lightspeedapp.com/API/V3/Account";
28
+ this.maxRetries = 3;
29
+ this.lastResponse = null;
30
+ }
31
+
32
+ handleError(msg, err) {
33
+ console.error(`${msg} - ${err}`);
34
+ throw err;
35
+ }
36
+
37
+ setLastResponse = (response) => (this.lastResponse = response);
38
+
39
+ handleRateLimit = async (options) => {
40
+ if (!this.lastResponse) return null;
41
+
42
+ const { method } = options;
43
+
44
+ const requestUnits = getRequestUnits(method);
45
+ const rateHeader = this.lastResponse.headers["x-ls-api-bucket-level"];
46
+
47
+ if (!rateHeader) return null;
48
+
49
+ const [used, available] = rateHeader.split("/");
50
+ const availableUnits = available - used;
51
+ if (requestUnits <= availableUnits) return 0;
52
+
53
+ const dripRate = this.lastResponse.headers["x-ls-api-drip-rate"];
54
+ const unitWait = requestUnits - availableUnits;
55
+ const delay = Math.ceil((unitWait / dripRate) * 1000);
56
+ await sleep(delay);
57
+
58
+ return unitWait;
59
+ };
60
+
61
+ getToken = async () => {
62
+ const body = {
63
+ grant_type: "refresh_token",
64
+ client_id: this.clientID,
65
+ client_secret: this.clientSecret,
66
+ refresh_token: this.refreshToken,
67
+ };
68
+
69
+ const response = await axios({
70
+ url: "https://cloud.lightspeedapp.com/oauth/access_token.php",
71
+ method: "post",
72
+ headers: {
73
+ "Content-Type": "application/json",
74
+ },
75
+ data: JSON.stringify(body),
76
+ }).catch((error) => console.error(error.response.data));
77
+
78
+ const tokenData = await response.data;
79
+ const token = tokenData.access_token;
80
+
81
+ return token;
82
+ };
83
+
84
+ getResource = async (options, retries = 0) => {
85
+ this.handleRateLimit(options);
86
+
87
+ const token = await this.getToken();
88
+
89
+ if (!token) throw new Error("Error Fetching Token");
90
+
91
+ options.headers = {
92
+ Authorization: `Bearer ${token}`,
93
+ "Content-Type": "application/json",
94
+ };
95
+
96
+ try {
97
+ const res = await axios(options);
98
+ this.lastResponse = res;
99
+ return {
100
+ data: res.data,
101
+ next: res.next,
102
+ previous: res.previous,
103
+ };
104
+ } catch (err) {
105
+ if (retries < this.maxRetries) {
106
+ console.log(`Error: ${err}, retrying in 2 seconds...`);
107
+ await new Promise((resolve) => setTimeout(resolve, 2000));
108
+ return await this.getResource(url, retries + 1);
109
+ } else {
110
+ console.error(`Failed Request statusText: `, res.statusText);
111
+ console.log(`Failed data: `, response.data);
112
+ throw err;
113
+ }
114
+ }
115
+ };
116
+
117
+ async getAllData(options) {
118
+ let allData = [];
119
+ while (options.url) {
120
+ const { data } = await this.getResource(options);
121
+ let next = data["@attributes"].next;
122
+ let selectDataArray = Object.keys(data)[1];
123
+ let selectedData = data[selectDataArray];
124
+ allData = allData.concat(selectedData);
125
+ options.url = next;
126
+ }
127
+ // console.log(allData);
128
+ return allData;
129
+ }
130
+
131
+ async getCustomers(relations) {
132
+ const options = {
133
+ url: `${this.baseUrl}/${this.accountID}/Customer.json`,
134
+ method: "GET",
135
+ };
136
+
137
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
138
+
139
+ try {
140
+ const response = await this.getAllData(options);
141
+ return response;
142
+ } catch (error) {
143
+ return this.handleError("GET CUSTOMERS ERROR", error.response);
144
+ }
145
+ }
146
+
147
+ async getItems(relations) {
148
+ const options = {
149
+ url: `${this.baseUrl}/${this.accountID}/Item.json`,
150
+ method: "GET",
151
+ };
152
+
153
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
154
+
155
+ try {
156
+ const response = await this.getAllData(options);
157
+ return response;
158
+ } catch (error) {
159
+ return this.handleError("GET ITEMS ERROR", error.response);
160
+ }
161
+ }
162
+
163
+ async getItem(itemID, relations) {
164
+ const options = {
165
+ url: `${this.baseUrl}/${this.accountID}/Item/${itemID}.json`,
166
+ method: "GET",
167
+ };
168
+
169
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
170
+
171
+ try {
172
+ const response = await this.getAllData(options);
173
+ return response;
174
+ } catch (error) {
175
+ return this.handleError("GET ITEM ERROR", error.response);
176
+ }
177
+ }
178
+
179
+ async getCategories(relations) {
180
+ const options = {
181
+ url: `${this.baseUrl}/${this.accountID}/Category.json`,
182
+ method: "GET",
183
+ };
184
+
185
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
186
+
187
+ try {
188
+ const response = await this.getAllData(options);
189
+ return response;
190
+ } catch (error) {
191
+ return this.handleError("GET CATEGORIES ERROR", error.response);
192
+ }
193
+ }
194
+
195
+ async getCategory(categoryID, relations) {
196
+ const options = {
197
+ url: `${this.baseUrl}/${this.accountID}/Category/${categoryID}.json`,
198
+ method: "GET",
199
+ };
200
+
201
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
202
+
203
+ try {
204
+ const response = await this.getAllData(options);
205
+ return response;
206
+ } catch (error) {
207
+ return this.handleError("GET CATEGORY ERROR", error.response);
208
+ }
209
+ }
210
+
211
+ async getManufacturers(relations) {
212
+ const options = {
213
+ url: `${this.baseUrl}/${this.accountID}/Category.json`,
214
+ method: "GET",
215
+ };
216
+
217
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
218
+
219
+ try {
220
+ const response = await this.getAllData(options);
221
+ return response;
222
+ } catch (error) {
223
+ return this.handleError("GET MANUFACTURERS ERROR", error.response);
224
+ }
225
+ }
226
+
227
+ async getManufacturer(manufacturerID, relations) {
228
+ const options = {
229
+ url: `${this.baseUrl}/${this.accountID}/Manufacturer/${manufacturerID}.json`,
230
+ method: "GET",
231
+ };
232
+
233
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
234
+
235
+ try {
236
+ const response = await this.getAllData(options);
237
+ return response;
238
+ } catch (error) {
239
+ return this.handleError("GET MANUFACTURER ERROR", error.response);
240
+ }
241
+ }
242
+
243
+ async getOrders(relations) {
244
+ const options = {
245
+ url: `${this.baseUrl}/${this.accountID}/Order.json`,
246
+ method: "GET",
247
+ };
248
+
249
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
250
+
251
+ try {
252
+ const response = await this.getAllData(options);
253
+ return response;
254
+ } catch (error) {
255
+ return this.handleError("GET ORDERS ERROR", error.response);
256
+ }
257
+ }
258
+
259
+ async getOrder(orderID, relations) {
260
+ const options = {
261
+ url: `${this.baseUrl}/${this.accountID}/Order/${orderID}.json`,
262
+ method: "GET",
263
+ };
264
+
265
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
266
+
267
+ try {
268
+ const response = await this.getAllData(options);
269
+ return response;
270
+ } catch (error) {
271
+ return this.handleError("GET ORDER ERROR", error.response);
272
+ }
273
+ }
274
+
275
+ async getVendors(relations) {
276
+ const options = {
277
+ url: `${this.baseUrl}/${this.accountID}/Vendor.json`,
278
+ method: "GET",
279
+ };
280
+
281
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
282
+
283
+ try {
284
+ const response = await this.getAllData(options);
285
+ return response;
286
+ } catch (error) {
287
+ return this.handleError("GET VENDORS ERROR", error.response);
288
+ }
289
+ }
290
+
291
+ async getVendor(vendorID, relations) {
292
+ const options = {
293
+ url: `${this.baseUrl}/${this.accountID}/Vendor/${vendorID}.json`,
294
+ method: "GET",
295
+ };
296
+
297
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
298
+
299
+ try {
300
+ const response = await this.getAllData(options);
301
+ return response;
302
+ } catch (error) {
303
+ return this.handleError("GET VENDOR ERROR", error.response);
304
+ }
305
+ }
306
+
307
+ async getSales(relations) {
308
+ const options = {
309
+ url: `${this.baseUrl}/${this.accountID}/Sale.json`,
310
+ method: "GET",
311
+ };
312
+
313
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
314
+
315
+ try {
316
+ const response = await this.getAllData(options);
317
+ return response;
318
+ } catch (error) {
319
+ return this.handleError("GET SALES ERROR", error.response);
320
+ }
321
+ }
322
+
323
+ async getSale(saleID, relations) {
324
+ const options = {
325
+ url: `${this.baseUrl}/${this.accountID}/Sale/${saleID}.json`,
326
+ method: "GET",
327
+ };
328
+
329
+ if (relations) options.url = options.url + `?load_relations=${relations}`;
330
+
331
+ try {
332
+ const response = await this.getAllData(options);
333
+ return response;
334
+ } catch (error) {
335
+ return this.handleError("GET SALE ERROR", error.response);
336
+ }
337
+ }
338
+ }
339
+
340
+ export default LightspeedRetailSDK;
package/package.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "lightspeed-retail-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Another unofficial Lightspeed Retail API SDK for Node.js",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
8
+ },
9
+ "author": "@darrylmorley",
10
+ "license": "ISC",
11
+ "dependencies": {
12
+ "axios": "^1.2.2"
13
+ },
14
+ "type": "module"
15
+ }