@scryme/sdk 9.71.2-next.0 → 9.72.0-next.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 +264 -131
- package/dist/{base-CoBANk9F.d.mts → base-BpmvtAH4.d.mts} +278 -101
- package/dist/{base-CoBANk9F.d.ts → base-BpmvtAH4.d.ts} +278 -101
- package/dist/{chunk-Z6VKSRBE.mjs → chunk-TNAWGB5N.mjs} +445 -389
- package/dist/client.d.mts +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +445 -389
- package/dist/client.mjs +1 -1
- package/dist/index.d.mts +52 -44
- package/dist/index.d.ts +52 -44
- package/dist/index.js +445 -389
- package/dist/index.mjs +1 -1
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +445 -389
- package/dist/server.mjs +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,171 +1,331 @@
|
|
|
1
1
|
# Scryme V3 SDK
|
|
2
2
|
|
|
3
|
-
The official TypeScript SDK for the **Scryme V3 API**—engineered for scalability, security, and developer convenience. This SDK provides complete, type-safe coverage of all Scryme V3 services, allowing developers to seamlessly integrate
|
|
3
|
+
The official TypeScript SDK for the **Scryme V3 API**—engineered for scalability, security, type safety, and developer convenience. This SDK provides complete, type-safe coverage of all Scryme V3 services, allowing developers to seamlessly integrate storefronts, mobile apps, connected applications, and server-side workflows with the Scryme ecosystem.
|
|
4
4
|
|
|
5
5
|
For the complete, interactive documentation, live sandbox playgrounds, and detailed OpenAPI schemas, please visit our documentation portal:
|
|
6
6
|
👉 **[https://docs.scryme.tech](https://docs.scryme.tech)**
|
|
7
7
|
|
|
8
|
+
---
|
|
9
|
+
|
|
8
10
|
## 🚀 Features
|
|
9
11
|
|
|
10
12
|
- **End-to-End Type Safety**: Direct compilation from the core OpenAPI 3.0 specification.
|
|
11
|
-
- **Axios-Based Client**: Built-in support for request/response interceptors, customizable base URLs, and timeout configurations.
|
|
12
13
|
- **Strictly Isolated Client & Server SDKs**: Prevents request state and session pollution in multi-tenant environments.
|
|
13
|
-
- **
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
14
|
+
- **Stateful Customer Engine & Auth**: Full support for customer registration (`signUp`), credential sign-in (`signIn`), proactive/reactive token refresh, active session tracking, multi-session revocation, and reactive React hooks (`useSession`).
|
|
15
|
+
- **Stateful Shopping Cart**: Dynamic `customerId` resolution, automatic line item difference calculations (`cart.update`), totals metrics, guest-to-customer cart deep-merging (`mergeGuestCart`), and multi-location checkout.
|
|
16
|
+
- **Axios-Based Network Layer**: Interceptor-based automatic token exchange, proactive/reactive expiration handling, and multi-tenant `orgSlug` auto-injection.
|
|
17
|
+
- **Comprehensive Domain Coverage**:
|
|
18
|
+
- **Auth & Customer Engine**: Customer login, token refresh, active session management, address directory, and client credentials flow.
|
|
19
|
+
- **Catalog & Services**: Products, services, CMS customizations, product reviews with auto-resolved customer context, service categories, resources, availability slots, and public OTP booking flows.
|
|
20
|
+
- **Orders & Cart**: Stateful shopping cart, sales orders, quote requests, B2B quotes, STK push payments, and checkout.
|
|
21
|
+
- **Inventory**: Stock queries, multi-branch listings, batch tracking (trace, split, merge), B2B availability checks, physical reconciliation, assemblies, and lead time/waste analysis.
|
|
22
|
+
- **CRM**: Custom CRM record definitions, custom fields, relationships, associations, notes logging, and timeline activities.
|
|
23
|
+
- **Loyalty**: Loyalty status (tiers & points), voucher validation, customer favorites, and reward redemption.
|
|
24
|
+
- **Finance & Accounting**: Invoices, corporate expenses, utility account tracking, petty cash funds, Profit & Loss reports, balance sheets, and cash flow analysis.
|
|
25
|
+
- **POS**: POS device provisioning, standalone POS keys, staff login, terminal sync, and petty cash expense logging.
|
|
26
|
+
- **Members & Roles**: Staff rosters, custom roles, permission sets, departments, invitations, and attendance logging (check-in/check-out).
|
|
27
|
+
- **Integrations & Webhooks**: Subscription management, Strapi E-commerce integration, and Windmill workflow callback orchestration.
|
|
24
28
|
|
|
25
29
|
---
|
|
26
30
|
|
|
27
31
|
## 📦 Installation
|
|
28
32
|
|
|
29
|
-
|
|
33
|
+
Install `@scryme/sdk` into your project:
|
|
30
34
|
|
|
31
35
|
```bash
|
|
32
36
|
pnpm add @scryme/sdk
|
|
37
|
+
# or
|
|
38
|
+
npm install @scryme/sdk
|
|
39
|
+
# or
|
|
40
|
+
yarn add @scryme/sdk
|
|
33
41
|
```
|
|
34
42
|
|
|
35
43
|
---
|
|
36
44
|
|
|
37
|
-
## 🔑 Client & Server SDK
|
|
45
|
+
## 🔑 Client & Server SDK Architecture
|
|
38
46
|
|
|
39
|
-
The SDK
|
|
47
|
+
The SDK provides distinct, fully isolated client-side (`@scryme/sdk/client`) and server-side (`@scryme/sdk/server`) constructors to prevent request state and session pollution in multi-tenant environments.
|
|
48
|
+
|
|
49
|
+
---
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
Strictly isolates requests and Axios instances. Ideal for Next.js API routes, edge functions, backend microservices, or Windmill workflows.
|
|
51
|
+
## 📱 Client-Side Setup (`@scryme/sdk/client`)
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
The `ScrymeClientSDK` provides stateful session persistence (`localStorage` or custom `StorageProvider`), reactive auth event listeners, and stateful customer cart operations.
|
|
54
|
+
|
|
55
|
+
### Initialization
|
|
46
56
|
|
|
47
57
|
```typescript
|
|
48
|
-
import {
|
|
58
|
+
import { ScrymeClientSDK, createClientSDK } from "@scryme/sdk/client";
|
|
49
59
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
60
|
+
// Class-based constructor
|
|
61
|
+
const scrymeClient = new ScrymeClientSDK({
|
|
62
|
+
clientId: "storefront_client_id",
|
|
63
|
+
orgSlug: "my-organization",
|
|
64
|
+
baseURL: "https://api.scryme.tech", // Optional, defaults to https://api.scryme.tech
|
|
55
65
|
});
|
|
56
66
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
67
|
+
// Or using factory helper
|
|
68
|
+
const scrymeClientAlt = createClientSDK({
|
|
69
|
+
clientId: "storefront_client_id",
|
|
70
|
+
orgSlug: "my-organization",
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 👤 Customer Authentication & Session Engine (`scrymeClient.customer.auth`)
|
|
77
|
+
|
|
78
|
+
`ScrymeClientSDK` includes a stateful Customer Authentication engine that manages customer login credentials, local session storage, JWT auto-refresh, active session tracking, and reactive UI hooks.
|
|
79
|
+
|
|
80
|
+
### Customer Registration & Sign-Up (`signUp`)
|
|
81
|
+
|
|
82
|
+
Registers a new customer profile. If a `password` is provided, `signUp` automatically signs the customer in and initializes an active customer session:
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const response = await scrymeClient.customer.auth.signUp({
|
|
86
|
+
name: "Jane Smith",
|
|
87
|
+
email: "jane.smith@example.com",
|
|
88
|
+
password: "securepassword123", // Establishes customer login credentials
|
|
89
|
+
phone: "+254700000123",
|
|
90
|
+
company: "Acme Commerce Inc",
|
|
91
|
+
customerType: "B2B_PREMIUM",
|
|
92
|
+
taxId: "PIN-KRA-123456",
|
|
93
|
+
address: {
|
|
94
|
+
label: "Headquarters",
|
|
95
|
+
street1: "123 Commercial Way",
|
|
96
|
+
city: "Nairobi",
|
|
97
|
+
country: "Kenya",
|
|
98
|
+
isDefault: true,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log("Customer registered:", response.data);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Customer Sign-In (`signIn`)
|
|
106
|
+
|
|
107
|
+
Authenticates a customer using email and password, starting an active session in Redis, saving the JWT token to storage, and firing a `SIGNED_IN` event:
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
try {
|
|
111
|
+
const authResponse = await scrymeClient.customer.auth.signIn({
|
|
112
|
+
email: "jane.smith@example.com",
|
|
113
|
+
password: "securepassword123",
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
console.log("Bearer Token:", authResponse.token);
|
|
117
|
+
console.log("Session Metadata:", authResponse.session);
|
|
118
|
+
console.log("Customer Profile:", authResponse.user);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error("Login failed:", error);
|
|
61
121
|
}
|
|
62
122
|
```
|
|
63
123
|
|
|
64
|
-
|
|
124
|
+
### Sign-Out (`signOut`)
|
|
125
|
+
|
|
126
|
+
Clears persisted session tokens, resets memory state, and fires a `SIGNED_OUT` event:
|
|
65
127
|
|
|
66
128
|
```typescript
|
|
67
|
-
|
|
129
|
+
await scrymeClient.customer.auth.signOut();
|
|
130
|
+
```
|
|
68
131
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
132
|
+
### Session Refresh & Inspection
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// Explicitly refresh the current customer session
|
|
136
|
+
const freshSession = await scrymeClient.customer.auth.refreshSession();
|
|
137
|
+
|
|
138
|
+
// Get internal session state synchronously/asynchronously
|
|
139
|
+
const sessionState = await scrymeClient.customer.auth.getSession();
|
|
140
|
+
console.log("Token:", sessionState.token);
|
|
141
|
+
console.log("Active User:", sessionState.user);
|
|
142
|
+
|
|
143
|
+
// Listen for authentication state changes (SIGNED_IN, SIGNED_OUT, INITIAL_SESSION)
|
|
144
|
+
const { unsubscribe } = scrymeClient.customer.auth.onAuthStateChange((event, session) => {
|
|
145
|
+
console.log(`Auth state event: ${event}`, session);
|
|
74
146
|
});
|
|
147
|
+
// Clean up listener when done
|
|
148
|
+
unsubscribe();
|
|
75
149
|
```
|
|
76
150
|
|
|
77
|
-
###
|
|
78
|
-
Provides stateful and reactive state persistence (localStorage / StorageProviders) with login listeners and automatic session recoveries.
|
|
151
|
+
### Concurrent Session Revocation
|
|
79
152
|
|
|
80
|
-
|
|
81
|
-
Requires `clientId`, `clientSecret`, and `orgSlug` strictly to initialize correctly:
|
|
153
|
+
Customers can view and destroy active sessions across devices:
|
|
82
154
|
|
|
83
155
|
```typescript
|
|
84
|
-
|
|
156
|
+
// List all active concurrent sessions
|
|
157
|
+
const sessions = await scrymeClient.customer.auth.getSessions();
|
|
158
|
+
console.log("Active sessions count:", sessions.length);
|
|
85
159
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
160
|
+
// Revoke a specific session by ID
|
|
161
|
+
await scrymeClient.customer.auth.revokeSession("sess_abc12345");
|
|
162
|
+
|
|
163
|
+
// Revoke all other sessions except the active one
|
|
164
|
+
await scrymeClient.customer.auth.revokeAllSessions("other");
|
|
165
|
+
|
|
166
|
+
// Revoke all sessions
|
|
167
|
+
await scrymeClient.customer.auth.revokeAllSessions();
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Reactive React Hook (`useSession`)
|
|
171
|
+
|
|
172
|
+
React storefront components can use `useSession` for immediate, real-time synchronization with customer authentication state:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import React from "react";
|
|
176
|
+
import { scrymeClient } from "./scryme";
|
|
177
|
+
|
|
178
|
+
export function UserProfileHeader() {
|
|
179
|
+
const { data, isPending, error, refetch } = scrymeClient.customer.auth.useSession();
|
|
180
|
+
|
|
181
|
+
if (isPending) return <div>Loading customer session...</div>;
|
|
182
|
+
if (error) return <div>Error loading session: {error.message}</div>;
|
|
183
|
+
if (!data?.user) return <div>Welcome, Guest! <button onClick={() => scrymeClient.customer.auth.signIn(...)}>Sign In</button></div>;
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<div>
|
|
187
|
+
<span>Welcome back, {data.user.name}!</span>
|
|
188
|
+
<button onClick={() => scrymeClient.customer.auth.signOut()}>Sign Out</button>
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 👤 Customer Profile & Address Directory (`scrymeClient.customer`)
|
|
197
|
+
|
|
198
|
+
Manage the logged-in customer's profile details and address book:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Fetch current logged-in customer profile
|
|
202
|
+
const profile = await scrymeClient.customer.getProfile();
|
|
203
|
+
console.log("Profile:", profile);
|
|
204
|
+
|
|
205
|
+
// Update profile details
|
|
206
|
+
const updated = await scrymeClient.customer.updateProfile({
|
|
207
|
+
phone: "+254799999999",
|
|
208
|
+
company: "Acme Holdings Ltd",
|
|
90
209
|
});
|
|
91
210
|
|
|
92
|
-
//
|
|
93
|
-
scrymeClient.
|
|
94
|
-
|
|
211
|
+
// Manage saved addresses
|
|
212
|
+
const addresses = await scrymeClient.customer.getAddresses();
|
|
213
|
+
await scrymeClient.customer.addAddress({
|
|
214
|
+
label: "Office Branch",
|
|
215
|
+
street1: "45 Westlands Rd",
|
|
216
|
+
city: "Nairobi",
|
|
217
|
+
country: "Kenya",
|
|
218
|
+
isDefault: false,
|
|
95
219
|
});
|
|
220
|
+
```
|
|
96
221
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 🛒 Stateful Shopping Cart Engine (`scrymeClient.cart`)
|
|
225
|
+
|
|
226
|
+
The SDK provides a stateful shopping cart submodule with automatic `customerId` resolution from active customer sessions, line item quantity delta calculation, and guest-to-customer cart deep merging.
|
|
227
|
+
|
|
228
|
+
### Dynamic `customerId` Resolution
|
|
229
|
+
For operations such as `cart.add`, `cart.remove`, `cart.update`, and `bookings.create`, the SDK automatically resolves `customerId` from the active user session (`user?.customerId || user?.id || user?.customer?.id`) if omitted from call arguments. If no authenticated customer session exists and no explicit `customerId` is passed, operations requiring customer context safely throw an error.
|
|
230
|
+
|
|
231
|
+
### Cart Mutations & Updates
|
|
232
|
+
|
|
233
|
+
```typescript
|
|
234
|
+
// 1. Fetch current active cart
|
|
235
|
+
const cartResponse = await scrymeClient.cart.get({ sessionId: "guest_session_123" });
|
|
236
|
+
|
|
237
|
+
// 2. Add product variant or service to cart
|
|
238
|
+
await scrymeClient.cart.add({
|
|
239
|
+
productId: "prod_sourdough_bread",
|
|
240
|
+
variantId: "var_large_500g",
|
|
241
|
+
quantity: 2,
|
|
242
|
+
sessionId: "guest_session_123", // Optional if customer is signed in
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
// 3. Smart quantity update (computes delta, calls add/remove appropriately)
|
|
246
|
+
await scrymeClient.cart.update({
|
|
247
|
+
productId: "prod_sourdough_bread",
|
|
248
|
+
variantId: "var_large_500g",
|
|
249
|
+
quantity: 5, // Automatically increments by +3
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// 4. Retrieve flat array of cart items
|
|
253
|
+
const items = await scrymeClient.cart.getItems();
|
|
254
|
+
|
|
255
|
+
// 5. Calculate summary metrics and total item count
|
|
256
|
+
const totals = await scrymeClient.cart.getTotals();
|
|
257
|
+
console.log("Total Items Count:", totals.itemsCount);
|
|
102
258
|
```
|
|
103
259
|
|
|
104
|
-
|
|
260
|
+
### Guest-to-Customer Cart Deep-Merging (`mergeGuestCart`)
|
|
261
|
+
|
|
262
|
+
When an anonymous guest customer signs in, call `mergeGuestCart` to aggregate their guest items into their permanent customer cart and clear the guest cart:
|
|
105
263
|
|
|
106
264
|
```typescript
|
|
107
|
-
|
|
265
|
+
// Sign in customer
|
|
266
|
+
await scrymeClient.customer.auth.signIn({
|
|
267
|
+
email: "john@example.com",
|
|
268
|
+
password: "password123",
|
|
269
|
+
});
|
|
108
270
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
271
|
+
// Merge guest cart items into customer cart
|
|
272
|
+
const mergedCart = await scrymeClient.cart.mergeGuestCart(
|
|
273
|
+
"guest_session_123", // Guest session ID
|
|
274
|
+
"cust_abc123" // Target customer ID
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
console.log("Merged Cart Items:", mergedCart.data.items);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Checkout & Sales Order Generation (`checkout`)
|
|
281
|
+
|
|
282
|
+
Converts the active shopping cart into an official enterprise Sales Order and deducts inventory at the target location:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
const order = await scrymeClient.cart.checkout({
|
|
286
|
+
locationId: "loc_nairobi_main",
|
|
287
|
+
notes: "Deliver before 2 PM",
|
|
288
|
+
channel: "ONLINE_STOREFRONT",
|
|
113
289
|
});
|
|
290
|
+
|
|
291
|
+
console.log("Sales Order Created:", order.id);
|
|
114
292
|
```
|
|
115
293
|
|
|
116
294
|
---
|
|
117
295
|
|
|
118
|
-
##
|
|
296
|
+
## 🌐 Server-Side Setup (`@scryme/sdk/server`)
|
|
119
297
|
|
|
120
|
-
|
|
298
|
+
The `ScrymeServerSDK` strictly isolates requests and Axios instances for multi-tenant server environments (Next.js Server Components, API routes, Windmill workflows, backend services).
|
|
121
299
|
|
|
122
|
-
###
|
|
300
|
+
### Initialization
|
|
123
301
|
|
|
124
302
|
```typescript
|
|
125
|
-
import {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
async function
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
clientSecret: process.env.SCRYME_CLIENT_SECRET || "your_secret"
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
const accessToken = tokenResponse.data.accessToken;
|
|
143
|
-
console.log("Successfully logged in! Token retrieved.");
|
|
144
|
-
|
|
145
|
-
// 3. Register the token in the Axios headers
|
|
146
|
-
axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
|
|
147
|
-
|
|
148
|
-
// 4. Perform type-safe V3 operations (orgSlug is auto-injected from environment!)
|
|
149
|
-
const products = await scryme.catalogGetProducts({ limit: 10 });
|
|
150
|
-
console.log("Catalog Products:", products.data);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
console.error("SDK execution failed:", error);
|
|
153
|
-
}
|
|
303
|
+
import { ScrymeServerSDK, createServerSDK } from "@scryme/sdk/server";
|
|
304
|
+
|
|
305
|
+
const scrymeServer = new ScrymeServerSDK({
|
|
306
|
+
baseURL: "https://api.scryme.tech",
|
|
307
|
+
orgSlug: "my-organization",
|
|
308
|
+
clientId: "your_client_id_123",
|
|
309
|
+
clientSecret: "your_client_secret_456",
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
async function runServerFlow() {
|
|
313
|
+
// Call APIs directly—the SDK handles Client Credentials token exchange and orgSlug auto-injection!
|
|
314
|
+
const products = await scrymeServer.catalog.getProducts({ limit: 10 });
|
|
315
|
+
const customers = await scrymeServer.admin.getCustomers({ limit: 20 });
|
|
316
|
+
console.log("Products count:", products.data.length);
|
|
154
317
|
}
|
|
155
318
|
```
|
|
156
319
|
|
|
157
|
-
For more domain examples, detailed schemas, and active playgrounds, head over to:
|
|
158
|
-
👉 **[docs.scryme.tech](https://docs.scryme.tech)**
|
|
159
|
-
|
|
160
320
|
---
|
|
161
321
|
|
|
162
|
-
## 🔒 Best Practices
|
|
322
|
+
## 🔒 Best Practices & Security
|
|
163
323
|
|
|
164
|
-
### Multi-
|
|
165
|
-
All
|
|
324
|
+
### Multi-Tenant Organization Scoping
|
|
325
|
+
All domain resources are isolated by organization. The SDK automatically injects the configured `orgSlug` parameter into all API calls.
|
|
166
326
|
|
|
167
|
-
###
|
|
168
|
-
When receiving
|
|
327
|
+
### Webhook Verification
|
|
328
|
+
When receiving webhooks from Scryme, verify payload HMAC signatures to prevent spoofing:
|
|
169
329
|
|
|
170
330
|
```typescript
|
|
171
331
|
import * as crypto from "crypto";
|
|
@@ -175,46 +335,19 @@ function verifySignature(
|
|
|
175
335
|
signature: string,
|
|
176
336
|
webhookSecret: string
|
|
177
337
|
): boolean {
|
|
178
|
-
// Generate the expected HMAC signature
|
|
179
338
|
const hmac = crypto.createHmac("sha256", webhookSecret);
|
|
180
339
|
const expectedSignature = hmac.update(payload).digest("hex");
|
|
181
340
|
|
|
182
|
-
// Pre-hash
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
.createHash("sha256")
|
|
186
|
-
.update(expectedSignature)
|
|
187
|
-
.digest();
|
|
188
|
-
const actualHash = crypto
|
|
189
|
-
.createHash("sha256")
|
|
190
|
-
.update(signature || "")
|
|
191
|
-
.digest();
|
|
341
|
+
// Pre-hash signatures to SHA-256 to guarantee equal length buffers and prevent timing attacks
|
|
342
|
+
const expectedHash = crypto.createHash("sha256").update(expectedSignature).digest();
|
|
343
|
+
const actualHash = crypto.createHash("sha256").update(signature || "").digest();
|
|
192
344
|
|
|
193
345
|
return crypto.timingSafeEqual(expectedHash, actualHash);
|
|
194
346
|
}
|
|
195
347
|
```
|
|
196
348
|
|
|
197
|
-
### Robust Error Handling
|
|
198
|
-
Scryme V3 API returns standardized error structures. Always wrap your SDK calls in `try/catch` blocks and parse the error fields to display helpful messages to users.
|
|
199
|
-
|
|
200
|
-
```typescript
|
|
201
|
-
import { isAxiosError } from "axios";
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
await scrymeClient.inventory.getInventory({ limit: 5 });
|
|
205
|
-
} catch (error) {
|
|
206
|
-
if (isAxiosError(error) && error.response) {
|
|
207
|
-
const apiError = error.response.data;
|
|
208
|
-
console.error(`Error (${apiError.error.code}): ${apiError.error.message}`);
|
|
209
|
-
console.error("Details:", apiError.error.details);
|
|
210
|
-
} else {
|
|
211
|
-
console.error("Unexpected Error:", error);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
349
|
---
|
|
217
350
|
|
|
218
351
|
## 📄 License
|
|
219
352
|
|
|
220
|
-
This package is licensed under the GNU Affero General Public License version 3 (AGPL-3.0). Please see the [LICENSE](../../LICENSE) file for
|
|
353
|
+
This package is licensed under the GNU Affero General Public License version 3 (AGPL-3.0). Please see the [LICENSE](../../LICENSE) file for details.
|