spaps-sdk 1.6.1 → 1.6.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/CHANGELOG.md +81 -0
- package/LICENSE +21 -0
- package/PERMISSIONS.md +390 -0
- package/README.md +88 -349
- package/package.json +8 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
- No changes yet.
|
|
10
|
+
|
|
11
|
+
## [1.6.1] - 2026-02-16
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Made the SDK `tsconfig` self-contained after root-config removal.
|
|
16
|
+
- Restored API-key header injection behavior for axios v1+ interceptor semantics.
|
|
17
|
+
|
|
18
|
+
## [1.6.0] - 2026-02-14
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- CFO roles, `company_id`, and affiliate-referral domain support.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Consolidated SPAPS integration patterns into published SDK packages.
|
|
27
|
+
|
|
28
|
+
## [1.5.1] - 2026-02-11
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Standardized local/dev config defaults, including port `3301`.
|
|
33
|
+
|
|
34
|
+
## [1.5.0] - 2026-01-10
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- Set-password endpoint support for authenticated users.
|
|
39
|
+
|
|
40
|
+
## [1.4.0] - 2026-01-10
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Email service integration with Mailgun support.
|
|
45
|
+
|
|
46
|
+
## [1.2.1] - 2025-12-31
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- Stripe webhook admin tooling for entitlement operations.
|
|
51
|
+
- CFO auth integration helpers.
|
|
52
|
+
|
|
53
|
+
## [1.1.52] - 2025-10-15
|
|
54
|
+
|
|
55
|
+
### Fixed
|
|
56
|
+
|
|
57
|
+
- Build and type-export stability across iterative CI packaging fixes.
|
|
58
|
+
|
|
59
|
+
## [1.1.0] - 2025-10-12
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- Admin API methods for Stripe product/price management and sync flows.
|
|
64
|
+
- Built-in permission utilities: `isAdminAccount`, `canAccessAdmin`, `getUserRole`.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- Subsequent patch iterations through `1.1.52` focused on CI and type-export stabilization.
|
|
69
|
+
|
|
70
|
+
## [1.0.0] - 2025-10-08
|
|
71
|
+
|
|
72
|
+
### Added
|
|
73
|
+
|
|
74
|
+
- Initial npm release as `spaps-sdk`.
|
|
75
|
+
- Zero-config client for SPAPS auth, payments, and permission checks.
|
|
76
|
+
- Local-mode auto-detection (`localhost`/`127.0.0.1`).
|
|
77
|
+
- Email/password, wallet, and magic-link authentication flows.
|
|
78
|
+
- Stripe checkout helpers with legal-consent support.
|
|
79
|
+
- Usage tracking and secure messaging helpers.
|
|
80
|
+
- TypeScript definitions with CJS/ESM dual exports.
|
|
81
|
+
- Node/browser/Next.js/React Native compatibility.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sweet Potato Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/PERMISSIONS.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# 🔐 SPAPS SDK Permission Utilities
|
|
2
|
+
|
|
3
|
+
Client-side permission checking and role management utilities for SPAPS applications.
|
|
4
|
+
|
|
5
|
+
## 🚀 Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
isAdminAccount,
|
|
10
|
+
getUserRole,
|
|
11
|
+
hasPermission,
|
|
12
|
+
canAccessAdmin,
|
|
13
|
+
PermissionChecker,
|
|
14
|
+
createPermissionChecker
|
|
15
|
+
} from 'spaps-sdk';
|
|
16
|
+
|
|
17
|
+
// Check if user is admin
|
|
18
|
+
const isAdmin = isAdminAccount('buildooor@gmail.com'); // true
|
|
19
|
+
|
|
20
|
+
// Get user role
|
|
21
|
+
const role = getUserRole('user@example.com'); // 'user' | 'admin' | 'guest'
|
|
22
|
+
|
|
23
|
+
// Check admin access
|
|
24
|
+
const adminCheck = canAccessAdmin(user);
|
|
25
|
+
if (adminCheck.allowed) {
|
|
26
|
+
// Show admin features
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check specific permissions
|
|
30
|
+
const canEdit = hasPermission(user, 'edit');
|
|
31
|
+
const canManage = hasPermission(user, ['create', 'delete']);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 📋 Default Admin Accounts
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const DEFAULT_ADMIN_ACCOUNTS = {
|
|
38
|
+
email: 'buildooor@gmail.com',
|
|
39
|
+
wallets: {
|
|
40
|
+
ethereum: '0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba',
|
|
41
|
+
solana: 'HVEbdiYU3Rr34NHBSgKs7q8cvdTeZLqNL77Z1FB2vjLy'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 🎯 React Component Examples
|
|
47
|
+
|
|
48
|
+
### Admin Button with Permission Checking
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import React from 'react';
|
|
52
|
+
import { canAccessAdmin, getUserDisplay } from 'spaps-sdk';
|
|
53
|
+
|
|
54
|
+
interface AdminButtonProps {
|
|
55
|
+
user: User | null;
|
|
56
|
+
onClick: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AdminButton({ user, onClick }: AdminButtonProps) {
|
|
60
|
+
const permissionCheck = canAccessAdmin(user);
|
|
61
|
+
const userDisplay = getUserDisplay(user);
|
|
62
|
+
|
|
63
|
+
if (!permissionCheck.allowed) {
|
|
64
|
+
return (
|
|
65
|
+
<button
|
|
66
|
+
disabled
|
|
67
|
+
title={permissionCheck.reason}
|
|
68
|
+
className="btn-disabled"
|
|
69
|
+
>
|
|
70
|
+
Admin Panel
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<button onClick={onClick} className="btn-primary">
|
|
77
|
+
{userDisplay.badge} Admin Panel
|
|
78
|
+
</button>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Protected Route Component
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import React from 'react';
|
|
87
|
+
import { canAccessAdmin, getRoleAwareErrorMessage } from 'spaps-sdk';
|
|
88
|
+
|
|
89
|
+
interface ProtectedRouteProps {
|
|
90
|
+
user: User | null;
|
|
91
|
+
children: React.ReactNode;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function AdminRoute({ user, children }: ProtectedRouteProps) {
|
|
95
|
+
const permissionCheck = canAccessAdmin(user);
|
|
96
|
+
|
|
97
|
+
if (!permissionCheck.allowed) {
|
|
98
|
+
const errorMessage = getRoleAwareErrorMessage(
|
|
99
|
+
'admin',
|
|
100
|
+
permissionCheck.userRole,
|
|
101
|
+
'access this page'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="error-container">
|
|
106
|
+
<h2>Access Denied</h2>
|
|
107
|
+
<p>{errorMessage}</p>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return <>{children}</>;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### User Display with Role Badge
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import React from 'react';
|
|
120
|
+
import { getUserDisplay } from 'spaps-sdk';
|
|
121
|
+
|
|
122
|
+
export function UserProfile({ user }: { user: User | null }) {
|
|
123
|
+
const display = getUserDisplay(user);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="user-profile">
|
|
127
|
+
<span>{display.displayName}</span>
|
|
128
|
+
{display.badge && (
|
|
129
|
+
<span className="role-badge">{display.badge}</span>
|
|
130
|
+
)}
|
|
131
|
+
<span className="role-text">({display.role})</span>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 🛠️ Advanced Usage
|
|
138
|
+
|
|
139
|
+
### Custom Permission Checker
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createPermissionChecker } from 'spaps-sdk';
|
|
143
|
+
|
|
144
|
+
// Create checker with custom admin accounts
|
|
145
|
+
const customAdmins = [
|
|
146
|
+
'admin@mycompany.com',
|
|
147
|
+
'0x1234567890abcdef...',
|
|
148
|
+
{
|
|
149
|
+
email: 'ceo@company.com',
|
|
150
|
+
wallets: {
|
|
151
|
+
ethereum: '0xabcdef...',
|
|
152
|
+
solana: 'ABC123...'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const checker = createPermissionChecker(customAdmins);
|
|
158
|
+
|
|
159
|
+
// Use the checker
|
|
160
|
+
const isAdmin = checker.isAdmin('admin@mycompany.com'); // true
|
|
161
|
+
const userDisplay = checker.getUserDisplay(user);
|
|
162
|
+
const canAccess = checker.canAccessAdmin(user);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Permission-Based UI Rendering
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import React from 'react';
|
|
169
|
+
import { hasPermission, PermissionChecker } from 'spaps-sdk';
|
|
170
|
+
|
|
171
|
+
const checker = new PermissionChecker();
|
|
172
|
+
|
|
173
|
+
function Dashboard({ user }: { user: User | null }) {
|
|
174
|
+
return (
|
|
175
|
+
<div>
|
|
176
|
+
<h1>Dashboard</h1>
|
|
177
|
+
|
|
178
|
+
{/* Show admin panel for admins only */}
|
|
179
|
+
{!checker.requiresAdmin(user) && (
|
|
180
|
+
<AdminPanel />
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{/* Show billing section for users with billing permission */}
|
|
184
|
+
{hasPermission(user, 'billing') && (
|
|
185
|
+
<BillingSection />
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{/* Show different content based on role */}
|
|
189
|
+
{checker.getRole(user?.email) === 'admin' ? (
|
|
190
|
+
<AdminDashboard />
|
|
191
|
+
) : (
|
|
192
|
+
<UserDashboard />
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Form with Permission Validation
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import React, { useState } from 'react';
|
|
203
|
+
import { hasPermission, getRoleAwareErrorMessage } from 'spaps-sdk';
|
|
204
|
+
|
|
205
|
+
function ProductForm({ user }: { user: User | null }) {
|
|
206
|
+
const [product, setProduct] = useState({ name: '', price: '' });
|
|
207
|
+
const canCreate = hasPermission(user, 'create');
|
|
208
|
+
|
|
209
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
|
|
212
|
+
if (!canCreate) {
|
|
213
|
+
const errorMessage = getRoleAwareErrorMessage(
|
|
214
|
+
'permission',
|
|
215
|
+
user ? 'user' : 'guest',
|
|
216
|
+
'create products'
|
|
217
|
+
);
|
|
218
|
+
alert(errorMessage);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Submit product
|
|
223
|
+
createProduct(product);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<form onSubmit={handleSubmit}>
|
|
228
|
+
<input
|
|
229
|
+
value={product.name}
|
|
230
|
+
onChange={(e) => setProduct({ ...product, name: e.target.value })}
|
|
231
|
+
placeholder="Product name"
|
|
232
|
+
/>
|
|
233
|
+
<input
|
|
234
|
+
value={product.price}
|
|
235
|
+
onChange={(e) => setProduct({ ...product, price: e.target.value })}
|
|
236
|
+
placeholder="Price"
|
|
237
|
+
/>
|
|
238
|
+
<button
|
|
239
|
+
type="submit"
|
|
240
|
+
disabled={!canCreate}
|
|
241
|
+
title={!canCreate ? 'Insufficient permissions' : undefined}
|
|
242
|
+
>
|
|
243
|
+
{canCreate ? 'Create Product' : 'Create Product (No Permission)'}
|
|
244
|
+
</button>
|
|
245
|
+
</form>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## 🧪 Testing Examples
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import {
|
|
254
|
+
isAdminAccount,
|
|
255
|
+
getUserRole,
|
|
256
|
+
canAccessAdmin,
|
|
257
|
+
createPermissionChecker
|
|
258
|
+
} from 'spaps-sdk';
|
|
259
|
+
|
|
260
|
+
describe('Permission Utilities', () => {
|
|
261
|
+
describe('isAdminAccount', () => {
|
|
262
|
+
it('should recognize default admin accounts', () => {
|
|
263
|
+
expect(isAdminAccount('buildooor@gmail.com')).toBe(true);
|
|
264
|
+
expect(isAdminAccount('0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba')).toBe(true);
|
|
265
|
+
expect(isAdminAccount('user@example.com')).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should recognize custom admin accounts', () => {
|
|
269
|
+
const customAdmins = ['admin@test.com'];
|
|
270
|
+
expect(isAdminAccount('admin@test.com', customAdmins)).toBe(true);
|
|
271
|
+
expect(isAdminAccount('user@test.com', customAdmins)).toBe(false);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('getUserRole', () => {
|
|
276
|
+
it('should return correct roles', () => {
|
|
277
|
+
expect(getUserRole('buildooor@gmail.com')).toBe('admin');
|
|
278
|
+
expect(getUserRole('user@example.com')).toBe('user');
|
|
279
|
+
expect(getUserRole()).toBe('guest');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('canAccessAdmin', () => {
|
|
284
|
+
it('should check admin access correctly', () => {
|
|
285
|
+
const adminUser = { id: '1', email: 'buildooor@gmail.com' };
|
|
286
|
+
const regularUser = { id: '2', email: 'user@example.com' };
|
|
287
|
+
|
|
288
|
+
expect(canAccessAdmin(adminUser).allowed).toBe(true);
|
|
289
|
+
expect(canAccessAdmin(regularUser).allowed).toBe(false);
|
|
290
|
+
expect(canAccessAdmin(null).allowed).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('PermissionChecker', () => {
|
|
295
|
+
it('should work with custom admins', () => {
|
|
296
|
+
const checker = createPermissionChecker(['admin@test.com']);
|
|
297
|
+
|
|
298
|
+
expect(checker.isAdmin('admin@test.com')).toBe(true);
|
|
299
|
+
expect(checker.getRole('admin@test.com')).toBe('admin');
|
|
300
|
+
expect(checker.canAccessAdmin({
|
|
301
|
+
id: '1',
|
|
302
|
+
email: 'admin@test.com'
|
|
303
|
+
}).allowed).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## 📚 API Reference
|
|
310
|
+
|
|
311
|
+
### Core Functions
|
|
312
|
+
|
|
313
|
+
#### `isAdminAccount(identifier: string, customAdmins?: AdminConfig[]): boolean`
|
|
314
|
+
Check if an identifier (email/wallet) is an admin account.
|
|
315
|
+
|
|
316
|
+
#### `getUserRole(identifier?: string, customAdmins?: AdminConfig[]): string`
|
|
317
|
+
Get user role: 'admin', 'user', or 'guest'.
|
|
318
|
+
|
|
319
|
+
#### `hasPermission(user: User | null, permissions: string | string[], customAdmins?: AdminConfig[]): boolean`
|
|
320
|
+
Check if user has specific permissions.
|
|
321
|
+
|
|
322
|
+
#### `canAccessAdmin(user: User | null, customAdmins?: AdminConfig[]): PermissionCheckResult`
|
|
323
|
+
Check if user can access admin features.
|
|
324
|
+
|
|
325
|
+
#### `getRoleAwareErrorMessage(requiredRole: string, userRole: string, action?: string): string`
|
|
326
|
+
Get contextual error message based on roles.
|
|
327
|
+
|
|
328
|
+
#### `getUserDisplay(user: User | null, customAdmins?: AdminConfig[])`
|
|
329
|
+
Get user display information with role indicators.
|
|
330
|
+
|
|
331
|
+
### PermissionChecker Class
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
class PermissionChecker {
|
|
335
|
+
constructor(customAdmins?: AdminConfig[]);
|
|
336
|
+
|
|
337
|
+
isAdmin(identifier: string): boolean;
|
|
338
|
+
getRole(identifier?: string): string;
|
|
339
|
+
hasPermission(user: User | null, permissions: string | string[]): boolean;
|
|
340
|
+
canAccessAdmin(user: User | null): PermissionCheckResult;
|
|
341
|
+
getErrorMessage(requiredRole: string, userRole: string, action?: string): string;
|
|
342
|
+
getUserDisplay(user: User | null);
|
|
343
|
+
|
|
344
|
+
// Convenience methods
|
|
345
|
+
requiresAuth(user: User | null): boolean;
|
|
346
|
+
requiresAdmin(user: User | null): boolean;
|
|
347
|
+
|
|
348
|
+
// Admin management
|
|
349
|
+
addCustomAdmin(admin: string | AdminConfig): void;
|
|
350
|
+
removeCustomAdmin(admin: string | AdminConfig): void;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Interfaces
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
interface User {
|
|
358
|
+
id: string;
|
|
359
|
+
email?: string;
|
|
360
|
+
wallet_address?: string;
|
|
361
|
+
role?: string;
|
|
362
|
+
permissions?: string[];
|
|
363
|
+
tier?: string;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
interface AdminConfig {
|
|
367
|
+
email: string;
|
|
368
|
+
wallets: {
|
|
369
|
+
ethereum: string;
|
|
370
|
+
solana: string;
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
interface PermissionCheckResult {
|
|
375
|
+
allowed: boolean;
|
|
376
|
+
reason?: string;
|
|
377
|
+
userRole: string;
|
|
378
|
+
requiredRole?: string;
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## 🌟 Best Practices
|
|
383
|
+
|
|
384
|
+
1. **Server-side validation**: Always validate permissions on the server
|
|
385
|
+
2. **Progressive enhancement**: Use client-side checks for UX, not security
|
|
386
|
+
3. **Consistent error messages**: Use `getRoleAwareErrorMessage` for user-friendly errors
|
|
387
|
+
4. **Role-based UI**: Show/hide features based on user permissions
|
|
388
|
+
5. **Custom admin management**: Use custom admin lists for multi-tenant apps
|
|
389
|
+
6. **Testing**: Test permission logic with various user states
|
|
390
|
+
7. **Type safety**: Use TypeScript interfaces for better development experience
|
package/README.md
CHANGED
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
id: spaps-typescript-sdk
|
|
3
|
-
title: spaps TypeScript SDK
|
|
4
|
-
category: sdk
|
|
5
|
-
tags:
|
|
6
|
-
- sdk
|
|
7
|
-
- typescript
|
|
8
|
-
- client
|
|
9
|
-
ai_summary: |
|
|
10
|
-
Reference for the spaps TypeScript SDK covering configuration, admin helpers,
|
|
11
|
-
and examples for interacting with authentication, payments, and permission
|
|
12
|
-
APIs from JavaScript or TypeScript applications.
|
|
13
|
-
last_updated: 2025-02-14
|
|
14
|
-
---
|
|
15
|
-
|
|
16
|
-
# @spaps/sdk
|
|
1
|
+
# spaps-sdk
|
|
17
2
|
|
|
18
3
|
<a href="https://www.npmjs.com/package/spaps-sdk"><img alt="npm" src="https://img.shields.io/npm/v/spaps-sdk.svg"></a>
|
|
19
4
|
<img alt="node" src="https://img.shields.io/badge/node-%3E%3D14-brightgreen">
|
|
@@ -21,71 +6,21 @@ last_updated: 2025-02-14
|
|
|
21
6
|
|
|
22
7
|
> Sweet Potato Authentication & Payment Service SDK
|
|
23
8
|
|
|
24
|
-
Zero-config client for SPAPS authentication, payments,
|
|
9
|
+
Zero-config client for SPAPS authentication, payments, secure messaging, and admin/product workflows.
|
|
25
10
|
|
|
26
|
-
##
|
|
11
|
+
## Quick Facts
|
|
27
12
|
|
|
28
|
-
|
|
13
|
+
- Package: `spaps-sdk`
|
|
14
|
+
- Latest version: `1.6.1`
|
|
15
|
+
- Runtime: `Node.js >=14.0.0`
|
|
16
|
+
- Default local API URL: `http://localhost:3301`
|
|
29
17
|
|
|
30
|
-
|
|
31
|
-
import { SPAPSClient } from 'spaps-sdk';
|
|
32
|
-
|
|
33
|
-
const spaps = new SPAPSClient({
|
|
34
|
-
apiKey: 'your-api-key',
|
|
35
|
-
apiUrl: 'https://api.sweetpotato.dev'
|
|
36
|
-
});
|
|
18
|
+
## Metadata
|
|
37
19
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
await spaps.admin.createProduct({
|
|
43
|
-
name: 'Premium Plan',
|
|
44
|
-
category: 'subscription',
|
|
45
|
-
description: 'Advanced features'
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
await spaps.admin.updateProduct('prod_123', {
|
|
49
|
-
name: 'Premium Plan Pro'
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
await spaps.admin.createPrice({
|
|
53
|
-
product_id: 'prod_123',
|
|
54
|
-
unit_amount: 2999,
|
|
55
|
-
currency: 'usd',
|
|
56
|
-
interval: 'month'
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Super admin operations
|
|
60
|
-
await spaps.admin.syncProducts();
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## 🔐 Permission Utilities
|
|
64
|
-
|
|
65
|
-
Built-in permission checking and role-based access control:
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { isAdminAccount, canAccessAdmin, getUserRole } from 'spaps-sdk';
|
|
69
|
-
|
|
70
|
-
// Check admin status
|
|
71
|
-
const isAdmin = isAdminAccount('buildooor@gmail.com'); // true
|
|
72
|
-
|
|
73
|
-
// Check admin access with detailed result
|
|
74
|
-
const adminCheck = canAccessAdmin(user);
|
|
75
|
-
if (adminCheck.allowed) {
|
|
76
|
-
// Show admin UI
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Get user role
|
|
80
|
-
const role = getUserRole('user@example.com'); // 'user' | 'admin' | 'guest'
|
|
81
|
-
|
|
82
|
-
// Check if authenticated user is admin
|
|
83
|
-
if (spaps.isAdmin(currentUser)) {
|
|
84
|
-
// Show admin features
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
See [PERMISSIONS.md](./PERMISSIONS.md) for React examples and complete documentation.
|
|
20
|
+
- `package_name`: `spaps-sdk`
|
|
21
|
+
- `latest_version`: `1.6.1`
|
|
22
|
+
- `minimum_runtime`: `Node.js >=14.0.0`
|
|
23
|
+
- `api_base_url`: `https://api.sweetpotato.dev`
|
|
89
24
|
|
|
90
25
|
## Installation
|
|
91
26
|
|
|
@@ -97,349 +32,153 @@ yarn add spaps-sdk
|
|
|
97
32
|
pnpm add spaps-sdk
|
|
98
33
|
```
|
|
99
34
|
|
|
100
|
-
### Compatibility
|
|
101
|
-
|
|
102
|
-
- ✅ **Node.js**: Fully supported (v14+) with automatic fetch polyfill
|
|
103
|
-
- ✅ **Browser**: Works in all modern browsers
|
|
104
|
-
- ✅ **TypeScript**: Full type definitions included
|
|
105
|
-
- ✅ **Next.js**: Server and client components supported
|
|
106
|
-
- ✅ **React Native**: Compatible with proper setup
|
|
107
|
-
|
|
108
|
-
The SDK automatically includes a `cross-fetch` polyfill for Node.js environments that don't have native fetch support.
|
|
109
|
-
|
|
110
35
|
## Quick Start
|
|
111
36
|
|
|
112
|
-
```
|
|
37
|
+
```typescript
|
|
113
38
|
import { SPAPSClient } from 'spaps-sdk';
|
|
114
|
-
// or
|
|
115
|
-
const { SPAPSClient } = require('spaps-sdk');
|
|
116
39
|
|
|
117
|
-
// Auto-detects local mode - no API key needed for localhost!
|
|
118
40
|
const spaps = new SPAPSClient({
|
|
119
|
-
apiUrl: 'http://localhost:3301'
|
|
41
|
+
apiUrl: 'http://localhost:3301',
|
|
120
42
|
});
|
|
121
43
|
|
|
122
|
-
// Login
|
|
123
44
|
const { data } = await spaps.login('user@example.com', 'password');
|
|
124
45
|
console.log('User:', data.user);
|
|
125
46
|
|
|
126
|
-
// Check authentication
|
|
127
47
|
if (spaps.isAuthenticated()) {
|
|
128
|
-
const
|
|
129
|
-
console.log(
|
|
48
|
+
const me = await spaps.getUser();
|
|
49
|
+
console.log(me.data.email);
|
|
130
50
|
}
|
|
131
51
|
```
|
|
132
52
|
|
|
133
|
-
##
|
|
134
|
-
|
|
135
|
-
- If `apiUrl` is omitted or points to `localhost`/`127.0.0.1`, the SDK runs in local mode.
|
|
136
|
-
- Local mode integrates seamlessly with the `spaps` CLI (`npx spaps local`), defaulting to `http://localhost:3301`.
|
|
137
|
-
- No API key is required in local mode; tokens and data are managed locally for development.
|
|
53
|
+
## Compatibility
|
|
138
54
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```
|
|
55
|
+
- Node.js (v14+)
|
|
56
|
+
- Modern browsers
|
|
57
|
+
- TypeScript (types bundled)
|
|
58
|
+
- Next.js and React Native
|
|
144
59
|
|
|
145
|
-
|
|
60
|
+
The SDK includes a `cross-fetch` polyfill for Node environments without native fetch.
|
|
146
61
|
|
|
147
|
-
|
|
62
|
+
## Configuration
|
|
148
63
|
|
|
149
|
-
|
|
150
|
-
- `SPAPS_API_KEY` — API key for production use
|
|
64
|
+
`SPAPSClient` reads config from constructor args first, then environment variables.
|
|
151
65
|
|
|
152
|
-
|
|
66
|
+
- `SPAPS_API_URL` or `NEXT_PUBLIC_SPAPS_API_URL` - API base URL
|
|
67
|
+
- `SPAPS_API_KEY` or `NEXT_PUBLIC_SPAPS_API_KEY` - API key
|
|
153
68
|
|
|
154
69
|
```bash
|
|
70
|
+
# Node
|
|
155
71
|
export SPAPS_API_URL=https://api.sweetpotato.dev
|
|
156
72
|
export SPAPS_API_KEY=spaps_xxx
|
|
157
|
-
```
|
|
158
73
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
```env
|
|
74
|
+
# Next.js
|
|
162
75
|
NEXT_PUBLIC_SPAPS_API_URL=https://api.sweetpotato.dev
|
|
163
76
|
```
|
|
164
77
|
|
|
165
|
-
|
|
78
|
+
Local mode auto-detection:
|
|
166
79
|
|
|
167
|
-
|
|
168
|
-
-
|
|
169
|
-
- **Auto-refreshes tokens** - Handles expired tokens automatically
|
|
170
|
-
- **TypeScript support** - Full type definitions included
|
|
171
|
-
- **Crypto payments ready** - Create invoices, poll status, verify webhooks, trigger reconciliation
|
|
80
|
+
- If `apiUrl` is omitted or points to `localhost`/`127.0.0.1`, local mode is enabled.
|
|
81
|
+
- In local mode, API key is optional.
|
|
172
82
|
|
|
173
|
-
|
|
174
|
-
```javascript
|
|
175
|
-
// Email/Password
|
|
176
|
-
await spaps.login(email, password);
|
|
177
|
-
await spaps.register(email, password);
|
|
83
|
+
## API Surface
|
|
178
84
|
|
|
179
|
-
|
|
180
|
-
|
|
85
|
+
- `auth` and top-level auth helpers: password, wallet, magic link, refresh/logout
|
|
86
|
+
- `sessions`: current/list/validate/revoke/touch
|
|
87
|
+
- `payments`: checkout sessions, products/prices, guest checkout, subscriptions, crypto invoices
|
|
88
|
+
- `secureMessages`: create/list secure messages
|
|
89
|
+
- `email`: send/list templates/get template/preview template
|
|
90
|
+
- `entitlements`: list/check/list by resource
|
|
91
|
+
- `admin`: product and price management helpers
|
|
92
|
+
- `dayrate`, `cfo`: specialized domain helpers
|
|
181
93
|
|
|
182
|
-
|
|
183
|
-
await spaps.refresh();
|
|
184
|
-
await spaps.logout();
|
|
185
|
-
|
|
186
|
-
// Get User
|
|
187
|
-
const user = await spaps.getUser();
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### 💳 Stripe Integration
|
|
191
|
-
```javascript
|
|
192
|
-
// Create checkout session with consent control
|
|
193
|
-
const session = await spaps.payments.createPaymentCheckout({
|
|
194
|
-
price_id: 'price_123',
|
|
195
|
-
success_url: 'https://app.example.com/success',
|
|
196
|
-
cancel_url: 'https://app.example.com/cancel',
|
|
197
|
-
require_legal_consent: true,
|
|
198
|
-
legal_consent_text: 'I agree I am 18+ and accept the HTMA Terms & Privacy.'
|
|
199
|
-
});
|
|
200
|
-
window.location.href = session.url;
|
|
94
|
+
## Permission Utilities
|
|
201
95
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
await spaps.cancelSubscription();
|
|
205
|
-
```
|
|
206
|
-
> `require_legal_consent` forces Stripe’s Terms/Privacy checkbox, and `legal_consent_text` (≤120 chars) customizes the copy. Text is sanitized and defaults to “I agree to the Terms of Service and Privacy Policy.” when omitted.
|
|
96
|
+
```typescript
|
|
97
|
+
import { isAdminAccount, canAccessAdmin, getUserRole } from 'spaps-sdk';
|
|
207
98
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const balance = await spaps.getUsageBalance();
|
|
212
|
-
console.log(`Credits: ${balance.data.balance}`);
|
|
99
|
+
const isAdmin = isAdminAccount('buildooor@gmail.com');
|
|
100
|
+
const role = getUserRole('user@example.com');
|
|
101
|
+
const adminCheck = canAccessAdmin({ id: 'u1', email: 'user@example.com' });
|
|
213
102
|
|
|
214
|
-
|
|
215
|
-
|
|
103
|
+
if (adminCheck.allowed) {
|
|
104
|
+
// render admin UI
|
|
105
|
+
}
|
|
216
106
|
```
|
|
217
107
|
|
|
218
|
-
|
|
219
|
-
```javascript
|
|
220
|
-
// Create a secure message (content encrypted server-side when pii_enabled)
|
|
221
|
-
const message = await spaps.secureMessages.create({
|
|
222
|
-
patientId: '3d6f0a51-8d77-4b38-8248-2d1b2f1f6c7f',
|
|
223
|
-
practitionerId: 'a3d7f431-6c9d-4cbc-9f78-4e5b6a7c8d9e',
|
|
224
|
-
content: 'Patient is experiencing intermittent headaches.',
|
|
225
|
-
metadata: { urgency: 'high' }
|
|
226
|
-
});
|
|
108
|
+
See [PERMISSIONS.md](./PERMISSIONS.md) for React-oriented examples.
|
|
227
109
|
|
|
228
|
-
|
|
229
|
-
const messages = await spaps.secureMessages.list();
|
|
230
|
-
console.log(messages[0].content);
|
|
231
|
-
```
|
|
232
|
-
> Ensure your application has `settings.pii_enabled = true` so payloads are encrypted automatically.
|
|
110
|
+
## Secure Messaging
|
|
233
111
|
|
|
234
112
|
```typescript
|
|
235
|
-
// Provide a strongly typed metadata shape for downstream usage
|
|
236
113
|
type SecureMessageMetadata = { urgency: 'low' | 'high'; tags?: string[] };
|
|
237
114
|
|
|
238
|
-
const spaps = new SPAPSClient<SecureMessageMetadata>({
|
|
115
|
+
const spaps = new SPAPSClient<SecureMessageMetadata>({
|
|
116
|
+
apiUrl: 'https://api.sweetpotato.dev',
|
|
117
|
+
apiKey: process.env.SPAPS_API_KEY,
|
|
118
|
+
});
|
|
239
119
|
|
|
240
120
|
await spaps.secureMessages.create({
|
|
241
121
|
patientId: '3d6f0a51-8d77-4b38-8248-2d1b2f1f6c7f',
|
|
242
122
|
practitionerId: 'a3d7f431-6c9d-4cbc-9f78-4e5b6a7c8d9e',
|
|
243
123
|
content: 'Follow up scheduled for next week.',
|
|
244
|
-
metadata: { urgency: 'low', tags: ['follow-up'] }
|
|
124
|
+
metadata: { urgency: 'low', tags: ['follow-up'] },
|
|
245
125
|
});
|
|
246
|
-
|
|
247
|
-
const typedMessages = await spaps.secureMessages.list();
|
|
248
|
-
typedMessages[0].metadata.urgency; // "low" | "high"
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
Need runtime validation too? Reuse the shared schemas that ship with the SDK:
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
import { z } from 'zod';
|
|
255
|
-
import { createSecureMessageRequestSchema } from 'spaps-sdk';
|
|
256
|
-
|
|
257
|
-
const secureMessageRequestSchema = createSecureMessageRequestSchema(
|
|
258
|
-
z.object({ urgency: z.enum(['low', 'high']), tags: z.array(z.string()).optional() })
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
secureMessageRequestSchema.parse({
|
|
262
|
-
patientId: '3d6f0a51-8d77-4b38-8248-2d1b2f1f6c7f',
|
|
263
|
-
practitionerId: 'a3d7f431-6c9d-4cbc-9f78-4e5b6a7c8d9e',
|
|
264
|
-
content: 'All clear.',
|
|
265
|
-
metadata: { urgency: 'low' }
|
|
266
|
-
});
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
## Configuration
|
|
270
|
-
|
|
271
|
-
### Production Mode
|
|
272
|
-
```javascript
|
|
273
|
-
const spaps = new SPAPSClient({
|
|
274
|
-
apiUrl: 'https://api.sweetpotato.dev',
|
|
275
|
-
apiKey: 'spaps_your_api_key_here',
|
|
276
|
-
timeout: 10000 // Optional timeout in ms
|
|
277
|
-
});
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
### Local Development Mode (Auto-detected)
|
|
281
|
-
```javascript
|
|
282
|
-
const spaps = new SPAPSClient();
|
|
283
|
-
// Automatically uses http://localhost:3301 with no API key
|
|
284
|
-
```
|
|
285
|
-
|
|
286
|
-
### Environment Variables
|
|
287
|
-
```bash
|
|
288
|
-
# .env
|
|
289
|
-
SPAPS_API_URL=https://api.sweetpotato.dev
|
|
290
|
-
SPAPS_API_KEY=spaps_your_api_key_here
|
|
291
|
-
|
|
292
|
-
# Next.js
|
|
293
|
-
NEXT_PUBLIC_SPAPS_API_URL=https://api.sweetpotato.dev
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
## Helper Methods
|
|
297
|
-
|
|
298
|
-
```javascript
|
|
299
|
-
// Check if authenticated
|
|
300
|
-
spaps.isAuthenticated() // boolean
|
|
301
|
-
|
|
302
|
-
// Get current access token
|
|
303
|
-
spaps.getAccessToken() // string | undefined
|
|
304
|
-
|
|
305
|
-
// Set access token manually
|
|
306
|
-
spaps.setAccessToken(token)
|
|
307
|
-
|
|
308
|
-
// Check if in local mode
|
|
309
|
-
spaps.isLocalMode() // boolean
|
|
310
|
-
|
|
311
|
-
// Health check
|
|
312
|
-
await spaps.health()
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
## Types
|
|
316
|
-
|
|
317
|
-
The SDK ships with full TypeScript definitions. For most apps, import types directly from `spaps-sdk`:
|
|
318
|
-
|
|
319
|
-
```ts
|
|
320
|
-
import { SPAPSClient, type User, type Session, type Role } from 'spaps-sdk';
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
For advanced or library use cases, you can also import canonical types from the standalone package:
|
|
324
|
-
|
|
325
|
-
```ts
|
|
326
|
-
import type {
|
|
327
|
-
User,
|
|
328
|
-
Session,
|
|
329
|
-
Role,
|
|
330
|
-
Permission,
|
|
331
|
-
TokenPayload,
|
|
332
|
-
} from 'spaps-types';
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
Notes:
|
|
336
|
-
- `spaps-sdk` re-exports commonly used types for convenience.
|
|
337
|
-
- `spaps-types` is useful when building framework integrations, server middleware, or utilities that need to share types without depending on the SDK runtime.
|
|
338
|
-
|
|
339
|
-
## Import Styles
|
|
340
|
-
|
|
341
|
-
All these work:
|
|
342
|
-
```javascript
|
|
343
|
-
// ES6 Import
|
|
344
|
-
import { SPAPSClient } from 'spaps-sdk';
|
|
345
|
-
import SPAPSClient from 'spaps-sdk';
|
|
346
|
-
|
|
347
|
-
// CommonJS
|
|
348
|
-
const { SPAPSClient } = require('spaps-sdk');
|
|
349
|
-
const SPAPSClient = require('spaps-sdk');
|
|
350
|
-
|
|
351
|
-
// Alternative names
|
|
352
|
-
import { SPAPS } from 'spaps-sdk';
|
|
353
|
-
import { SweetPotatoSDK } from 'spaps-sdk';
|
|
354
126
|
```
|
|
355
127
|
|
|
356
128
|
## Admin API Methods
|
|
357
129
|
|
|
358
|
-
The SDK includes comprehensive admin methods for Stripe product management:
|
|
359
|
-
|
|
360
|
-
### Product Management
|
|
361
|
-
|
|
362
130
|
```typescript
|
|
363
|
-
|
|
364
|
-
const product = await spaps.admin.createProduct({
|
|
131
|
+
await spaps.admin.createProduct({
|
|
365
132
|
name: 'Premium Plan',
|
|
366
133
|
category: 'subscription',
|
|
367
|
-
description: 'Advanced features
|
|
368
|
-
images: ['https://example.com/product-image.jpg'],
|
|
369
|
-
metadata: { feature_set: 'premium' },
|
|
370
|
-
active: true
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
// Update product (Admin required)
|
|
374
|
-
const updatedProduct = await spaps.admin.updateProduct('prod_123', {
|
|
375
|
-
name: 'Premium Plan Pro',
|
|
376
|
-
description: 'Updated features'
|
|
134
|
+
description: 'Advanced features',
|
|
377
135
|
});
|
|
378
136
|
|
|
379
|
-
|
|
380
|
-
await spaps.admin.deleteProduct('prod_123');
|
|
381
|
-
|
|
382
|
-
// Get products (includes admin metadata if user is admin)
|
|
383
|
-
const { data } = await spaps.admin.getProducts();
|
|
384
|
-
if (data.adminMetadata) {
|
|
385
|
-
console.log('Total revenue:', data.adminMetadata.total_revenue);
|
|
386
|
-
}
|
|
387
|
-
```
|
|
137
|
+
await spaps.admin.updateProduct('prod_123', { name: 'Premium Plan Pro' });
|
|
388
138
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
// Create price (Admin required)
|
|
393
|
-
const price = await spaps.admin.createPrice({
|
|
139
|
+
await spaps.admin.createPrice({
|
|
394
140
|
product_id: 'prod_123',
|
|
395
|
-
unit_amount: 2999,
|
|
141
|
+
unit_amount: 2999,
|
|
396
142
|
currency: 'usd',
|
|
397
143
|
interval: 'month',
|
|
398
|
-
interval_count: 1,
|
|
399
|
-
nickname: 'Monthly Premium'
|
|
400
144
|
});
|
|
401
145
|
```
|
|
402
146
|
|
|
403
|
-
|
|
147
|
+
## Helper Methods
|
|
404
148
|
|
|
405
149
|
```typescript
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
150
|
+
spaps.isAuthenticated(); // boolean
|
|
151
|
+
spaps.getAccessToken(); // string | undefined
|
|
152
|
+
spaps.setAccessToken('token');
|
|
153
|
+
spaps.isLocalMode(); // boolean
|
|
154
|
+
await spaps.health();
|
|
409
155
|
```
|
|
410
156
|
|
|
411
|
-
|
|
157
|
+
## Troubleshooting
|
|
412
158
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (spaps.isAdmin(user)) {
|
|
417
|
-
// Show admin UI components
|
|
418
|
-
console.log('User has admin privileges');
|
|
419
|
-
} else {
|
|
420
|
-
// Show regular user UI
|
|
421
|
-
console.log('Regular user');
|
|
422
|
-
}
|
|
423
|
-
```
|
|
159
|
+
- `401 Unauthorized`: ensure valid access token and API key for non-local mode.
|
|
160
|
+
- `403 Forbidden`: account lacks admin role for admin endpoints.
|
|
161
|
+
- Local mode confusion: call `spaps.isLocalMode()` and verify `apiUrl`.
|
|
424
162
|
|
|
425
|
-
##
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
console.error('Rate limited');
|
|
437
|
-
} else {
|
|
438
|
-
console.error('Error:', error.message);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
163
|
+
## Development
|
|
164
|
+
|
|
165
|
+
From repository root:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
cd packages/sdk
|
|
169
|
+
npm ci
|
|
170
|
+
npm run build
|
|
171
|
+
npm run test
|
|
172
|
+
npm run typecheck:readme
|
|
173
|
+
npm run test:readme
|
|
441
174
|
```
|
|
442
175
|
|
|
176
|
+
## Release
|
|
177
|
+
|
|
178
|
+
- Main publish flow: `.github/workflows/publish.yml` (`publish-sdk` job).
|
|
179
|
+
- Manual publish flow: `.github/workflows/npm-publish-manual.yml`.
|
|
180
|
+
- npm credential source: repository secret `NPM_TOKEN`.
|
|
181
|
+
|
|
443
182
|
## License
|
|
444
183
|
|
|
445
184
|
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spaps-sdk",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
4
4
|
"description": "Sweet Potato Authentication & Payment Service SDK - Zero-config client with built-in permission checking, role-based access control, and dayrate scheduling",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"build": "tsup --tsconfig tsconfig.json src/index.ts --format cjs,esm --dts --clean",
|
|
21
21
|
"dev": "tsup --tsconfig tsconfig.json src/index.ts --format cjs,esm --dts --watch",
|
|
22
22
|
"test": "vitest run",
|
|
23
|
+
"test:readme": "vitest run test/readme-snippets.test.ts",
|
|
24
|
+
"typecheck:readme": "tsc -p tsconfig.readme.json --noEmit",
|
|
23
25
|
"smoke-test": "npm run build && node smoke-test.js",
|
|
24
26
|
"prepublishOnly": "npm run build && npm run smoke-test"
|
|
25
27
|
},
|
|
@@ -36,10 +38,10 @@
|
|
|
36
38
|
"scheduling"
|
|
37
39
|
],
|
|
38
40
|
"author": "buildooor",
|
|
39
|
-
"license": "
|
|
41
|
+
"license": "MIT",
|
|
40
42
|
"repository": {
|
|
41
43
|
"type": "git",
|
|
42
|
-
"url": "https://github.com/build000r"
|
|
44
|
+
"url": "https://github.com/build000r/sweet-potato"
|
|
43
45
|
},
|
|
44
46
|
"homepage": "https://www.buildooor.com/services",
|
|
45
47
|
"bugs": {
|
|
@@ -66,7 +68,9 @@
|
|
|
66
68
|
},
|
|
67
69
|
"files": [
|
|
68
70
|
"dist",
|
|
69
|
-
"README.md"
|
|
71
|
+
"README.md",
|
|
72
|
+
"CHANGELOG.md",
|
|
73
|
+
"PERMISSIONS.md"
|
|
70
74
|
],
|
|
71
75
|
"publishConfig": {
|
|
72
76
|
"access": "public",
|