@mspkapps/auth-client 0.1.29 → 0.1.32
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 +367 -4
- package/package.json +1 -1
- package/src/AuthClient.js +65 -19
package/README.md
CHANGED
|
@@ -232,7 +232,160 @@ router.patch('/profile', requireAuth, async (req, res) => {
|
|
|
232
232
|
export default router;
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
-
### 1.6
|
|
235
|
+
### 1.6 Developer Data Routes (Backend)
|
|
236
|
+
|
|
237
|
+
The AuthClient provides APIs to fetch developer-specific data (groups, apps, users). The **developer ID is automatically extracted** from the API key & secret, so you don't need to pass it explicitly.
|
|
238
|
+
|
|
239
|
+
Example `src/routes/developerRoutes.js`:
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
import express from 'express';
|
|
243
|
+
import authclient from '../auth/authClient.js';
|
|
244
|
+
import { AuthError } from '@mspkapps/auth-client';
|
|
245
|
+
|
|
246
|
+
const router = express.Router();
|
|
247
|
+
|
|
248
|
+
function handleError(res, err, fallback = 'Request failed') {
|
|
249
|
+
if (err instanceof AuthError) {
|
|
250
|
+
return res.status(err.status || 400).json({
|
|
251
|
+
success: false,
|
|
252
|
+
message: err.message || fallback,
|
|
253
|
+
code: err.code || 'REQUEST_FAILED',
|
|
254
|
+
data: err.response?.data ?? null,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
console.error('Unexpected error:', err);
|
|
258
|
+
return res.status(500).json({
|
|
259
|
+
success: false,
|
|
260
|
+
message: fallback,
|
|
261
|
+
code: 'INTERNAL_ERROR',
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// GET /api/developer/groups
|
|
266
|
+
// Fetch all groups belonging to the authenticated developer
|
|
267
|
+
router.get('/groups', async (req, res) => {
|
|
268
|
+
try {
|
|
269
|
+
const resp = await authclient.getDeveloperGroups();
|
|
270
|
+
return res.json(resp);
|
|
271
|
+
} catch (err) {
|
|
272
|
+
return handleError(res, err, 'Failed to fetch groups');
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// GET /api/developer/apps?group_id=123
|
|
277
|
+
// Fetch developer's apps
|
|
278
|
+
// - No query params: returns ALL apps (with and without groups)
|
|
279
|
+
// - ?group_id=123: returns apps in specific group
|
|
280
|
+
// - ?group_id=null: returns only apps NOT in any group
|
|
281
|
+
router.get('/apps', async (req, res) => {
|
|
282
|
+
try {
|
|
283
|
+
const { group_id } = req.query;
|
|
284
|
+
|
|
285
|
+
let groupId;
|
|
286
|
+
if (group_id === 'null' || group_id === '') {
|
|
287
|
+
groupId = null; // Apps without groups
|
|
288
|
+
} else if (group_id !== undefined) {
|
|
289
|
+
groupId = group_id; // Specific group
|
|
290
|
+
} // else undefined = all apps
|
|
291
|
+
|
|
292
|
+
const resp = await authclient.getDeveloperApps(groupId);
|
|
293
|
+
return res.json(resp);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
return handleError(res, err, 'Failed to fetch apps');
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// GET /api/developer/users?app_id=123&page=1&limit=50
|
|
300
|
+
// Fetch users for a specific app (with pagination)
|
|
301
|
+
// Returns all user data EXCEPT password, including extra fields
|
|
302
|
+
router.get('/users', async (req, res) => {
|
|
303
|
+
try {
|
|
304
|
+
const { app_id, page = 1, limit = 50 } = req.query;
|
|
305
|
+
|
|
306
|
+
if (!app_id) {
|
|
307
|
+
return res.status(400).json({
|
|
308
|
+
success: false,
|
|
309
|
+
message: 'app_id query parameter is required'
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const resp = await authclient.getAppUsers({
|
|
314
|
+
appId: app_id,
|
|
315
|
+
page: parseInt(page),
|
|
316
|
+
limit: parseInt(limit)
|
|
317
|
+
});
|
|
318
|
+
return res.json(resp);
|
|
319
|
+
} catch (err) {
|
|
320
|
+
return handleError(res, err, 'Failed to fetch users');
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// GET /api/developer/user/:user_id
|
|
325
|
+
// Fetch specific user data by user ID
|
|
326
|
+
// Returns complete user data (no password) including extra fields
|
|
327
|
+
router.get('/user/:user_id', async (req, res) => {
|
|
328
|
+
try {
|
|
329
|
+
const { user_id } = req.params;
|
|
330
|
+
const resp = await authclient.getUserData(user_id);
|
|
331
|
+
return res.json(resp);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
return handleError(res, err, 'Failed to fetch user data');
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
export default router;
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Usage Examples:**
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
// Get all groups
|
|
344
|
+
GET /api/developer/groups
|
|
345
|
+
Response: { success: true, data: [...groups] }
|
|
346
|
+
|
|
347
|
+
// Get all apps (both in groups and standalone)
|
|
348
|
+
GET /api/developer/apps
|
|
349
|
+
Response: { success: true, data: [...apps] }
|
|
350
|
+
|
|
351
|
+
// Get apps in a specific group
|
|
352
|
+
GET /api/developer/apps?group_id=123
|
|
353
|
+
Response: { success: true, data: [...apps] }
|
|
354
|
+
|
|
355
|
+
// Get only apps NOT in any group
|
|
356
|
+
GET /api/developer/apps?group_id=null
|
|
357
|
+
Response: { success: true, data: [...apps] }
|
|
358
|
+
|
|
359
|
+
// Get users for an app (paginated)
|
|
360
|
+
GET /api/developer/users?app_id=456&page=1&limit=50
|
|
361
|
+
Response: {
|
|
362
|
+
success: true,
|
|
363
|
+
data: [...users],
|
|
364
|
+
pagination: {
|
|
365
|
+
currentPage: 1,
|
|
366
|
+
totalPages: 5,
|
|
367
|
+
totalUsers: 234,
|
|
368
|
+
limit: 50
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Get specific user
|
|
373
|
+
GET /api/developer/user/789
|
|
374
|
+
Response: {
|
|
375
|
+
success: true,
|
|
376
|
+
data: {
|
|
377
|
+
id: 789,
|
|
378
|
+
username: "john_doe",
|
|
379
|
+
email: "john@example.com",
|
|
380
|
+
name: "John Doe",
|
|
381
|
+
extra: { country: "USA", age: 30 },
|
|
382
|
+
is_email_verified: true,
|
|
383
|
+
// ... no password field
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### 1.7 Wire Up Routes in Backend App
|
|
236
389
|
|
|
237
390
|
In your backend `src/app.js`:
|
|
238
391
|
|
|
@@ -241,6 +394,7 @@ import express from 'express';
|
|
|
241
394
|
import cors from 'cors';
|
|
242
395
|
import authRoutes from './routes/authRoutes.js';
|
|
243
396
|
import userRoutes from './routes/userRoutes.js';
|
|
397
|
+
import developerRoutes from './routes/developerRoutes.js'; // NEW
|
|
244
398
|
|
|
245
399
|
const app = express();
|
|
246
400
|
|
|
@@ -249,6 +403,7 @@ app.use(express.json());
|
|
|
249
403
|
|
|
250
404
|
app.use('/api/auth', authRoutes);
|
|
251
405
|
app.use('/api/user', userRoutes);
|
|
406
|
+
app.use('/api/developer', developerRoutes); // NEW
|
|
252
407
|
|
|
253
408
|
export default app;
|
|
254
409
|
```
|
|
@@ -321,6 +476,61 @@ export async function apiGetProfile(accessToken) {
|
|
|
321
476
|
}
|
|
322
477
|
return json;
|
|
323
478
|
}
|
|
479
|
+
|
|
480
|
+
// Developer Data APIs (if building a developer dashboard)
|
|
481
|
+
export async function apiGetGroups() {
|
|
482
|
+
const resp = await fetch(`${API_BASE_URL}/api/developer/groups`, {
|
|
483
|
+
method: 'GET',
|
|
484
|
+
headers: { 'Content-Type': 'application/json' },
|
|
485
|
+
});
|
|
486
|
+
const json = await resp.json();
|
|
487
|
+
if (!resp.ok || json?.success === false) {
|
|
488
|
+
throw new Error(json?.message || 'Get groups failed');
|
|
489
|
+
}
|
|
490
|
+
return json;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export async function apiGetApps(groupId) {
|
|
494
|
+
const url = groupId !== undefined
|
|
495
|
+
? `${API_BASE_URL}/api/developer/apps?group_id=${groupId}`
|
|
496
|
+
: `${API_BASE_URL}/api/developer/apps`;
|
|
497
|
+
const resp = await fetch(url, {
|
|
498
|
+
method: 'GET',
|
|
499
|
+
headers: { 'Content-Type': 'application/json' },
|
|
500
|
+
});
|
|
501
|
+
const json = await resp.json();
|
|
502
|
+
if (!resp.ok || json?.success === false) {
|
|
503
|
+
throw new Error(json?.message || 'Get apps failed');
|
|
504
|
+
}
|
|
505
|
+
return json;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
export async function apiGetAppUsers(appId, page = 1, limit = 50) {
|
|
509
|
+
const resp = await fetch(
|
|
510
|
+
`${API_BASE_URL}/api/developer/users?app_id=${appId}&page=${page}&limit=${limit}`,
|
|
511
|
+
{
|
|
512
|
+
method: 'GET',
|
|
513
|
+
headers: { 'Content-Type': 'application/json' },
|
|
514
|
+
}
|
|
515
|
+
);
|
|
516
|
+
const json = await resp.json();
|
|
517
|
+
if (!resp.ok || json?.success === false) {
|
|
518
|
+
throw new Error(json?.message || 'Get users failed');
|
|
519
|
+
}
|
|
520
|
+
return json;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
export async function apiGetUserData(userId) {
|
|
524
|
+
const resp = await fetch(`${API_BASE_URL}/api/developer/user/${userId}`, {
|
|
525
|
+
method: 'GET',
|
|
526
|
+
headers: { 'Content-Type': 'application/json' },
|
|
527
|
+
});
|
|
528
|
+
const json = await resp.json();
|
|
529
|
+
if (!resp.ok || json?.success === false) {
|
|
530
|
+
throw new Error(json?.message || 'Get user data failed');
|
|
531
|
+
}
|
|
532
|
+
return json;
|
|
533
|
+
}
|
|
324
534
|
```
|
|
325
535
|
|
|
326
536
|
### 2.2 Simple Auth Context (Frontend-Only State)
|
|
@@ -490,6 +700,96 @@ function LoginPage() {
|
|
|
490
700
|
|
|
491
701
|
The Google ID token is sent to `/api/auth/google` on your backend, which then calls `authclient.googleAuth`.
|
|
492
702
|
|
|
703
|
+
### 2.5 Example Developer Dashboard Page (React Vite)
|
|
704
|
+
|
|
705
|
+
If you're building a developer dashboard that displays groups, apps, and users:
|
|
706
|
+
|
|
707
|
+
```jsx
|
|
708
|
+
import { useState, useEffect } from 'react';
|
|
709
|
+
import { apiGetGroups, apiGetApps, apiGetAppUsers } from '../services/authApi';
|
|
710
|
+
|
|
711
|
+
function DeveloperDashboard() {
|
|
712
|
+
const [groups, setGroups] = useState([]);
|
|
713
|
+
const [apps, setApps] = useState([]);
|
|
714
|
+
const [selectedApp, setSelectedApp] = useState(null);
|
|
715
|
+
const [users, setUsers] = useState([]);
|
|
716
|
+
const [loading, setLoading] = useState(true);
|
|
717
|
+
|
|
718
|
+
useEffect(() => {
|
|
719
|
+
loadData();
|
|
720
|
+
}, []);
|
|
721
|
+
|
|
722
|
+
const loadData = async () => {
|
|
723
|
+
try {
|
|
724
|
+
const [groupsResp, appsResp] = await Promise.all([
|
|
725
|
+
apiGetGroups(),
|
|
726
|
+
apiGetApps() // Get all apps
|
|
727
|
+
]);
|
|
728
|
+
setGroups(groupsResp.data);
|
|
729
|
+
setApps(appsResp.data);
|
|
730
|
+
} catch (err) {
|
|
731
|
+
console.error('Failed to load data:', err);
|
|
732
|
+
} finally {
|
|
733
|
+
setLoading(false);
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
const loadUsers = async (appId) => {
|
|
738
|
+
try {
|
|
739
|
+
const resp = await apiGetAppUsers(appId, 1, 50);
|
|
740
|
+
setUsers(resp.data);
|
|
741
|
+
setSelectedApp(appId);
|
|
742
|
+
} catch (err) {
|
|
743
|
+
console.error('Failed to load users:', err);
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
if (loading) return <div>Loading...</div>;
|
|
748
|
+
|
|
749
|
+
return (
|
|
750
|
+
<div>
|
|
751
|
+
<h1>Developer Dashboard</h1>
|
|
752
|
+
|
|
753
|
+
<section>
|
|
754
|
+
<h2>Groups ({groups.length})</h2>
|
|
755
|
+
<ul>
|
|
756
|
+
{groups.map(group => (
|
|
757
|
+
<li key={group.id}>{group.name}</li>
|
|
758
|
+
))}
|
|
759
|
+
</ul>
|
|
760
|
+
</section>
|
|
761
|
+
|
|
762
|
+
<section>
|
|
763
|
+
<h2>Apps ({apps.length})</h2>
|
|
764
|
+
<ul>
|
|
765
|
+
{apps.map(app => (
|
|
766
|
+
<li key={app.id}>
|
|
767
|
+
{app.app_name} {app.group_name && `(Group: ${app.group_name})`}
|
|
768
|
+
<button onClick={() => loadUsers(app.id)}>View Users</button>
|
|
769
|
+
</li>
|
|
770
|
+
))}
|
|
771
|
+
</ul>
|
|
772
|
+
</section>
|
|
773
|
+
|
|
774
|
+
{selectedApp && (
|
|
775
|
+
<section>
|
|
776
|
+
<h2>Users for App {selectedApp}</h2>
|
|
777
|
+
<ul>
|
|
778
|
+
{users.map(user => (
|
|
779
|
+
<li key={user.id}>
|
|
780
|
+
{user.email} - {user.name}
|
|
781
|
+
</li>
|
|
782
|
+
))}
|
|
783
|
+
</ul>
|
|
784
|
+
</section>
|
|
785
|
+
)}
|
|
786
|
+
</div>
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
export default DeveloperDashboard;
|
|
791
|
+
```
|
|
792
|
+
|
|
493
793
|
---
|
|
494
794
|
|
|
495
795
|
## 3. React Native CLI – Call Your Backend
|
|
@@ -524,6 +824,18 @@ export const apiRegister = (payload) => request('/api/auth/register', { method:
|
|
|
524
824
|
export const apiGoogleLogin = (idToken) =>
|
|
525
825
|
request('/api/auth/google', { method: 'POST', body: { id_token: idToken } });
|
|
526
826
|
export const apiGetProfile = (token) => request('/api/user/profile', { method: 'GET', token });
|
|
827
|
+
|
|
828
|
+
// Developer Data APIs
|
|
829
|
+
export const apiGetGroups = () => request('/api/developer/groups', { method: 'GET' });
|
|
830
|
+
export const apiGetApps = (groupId) => {
|
|
831
|
+
const path = groupId !== undefined
|
|
832
|
+
? `/api/developer/apps?group_id=${groupId}`
|
|
833
|
+
: '/api/developer/apps';
|
|
834
|
+
return request(path, { method: 'GET' });
|
|
835
|
+
};
|
|
836
|
+
export const apiGetAppUsers = (appId, page = 1, limit = 50) =>
|
|
837
|
+
request(`/api/developer/users?app_id=${appId}&page=${page}&limit=${limit}`, { method: 'GET' });
|
|
838
|
+
export const apiGetUserData = (userId) => request(`/api/developer/user/${userId}`, { method: 'GET' });
|
|
527
839
|
```
|
|
528
840
|
|
|
529
841
|
### 3.2 Auth Context (React Native)
|
|
@@ -655,13 +967,51 @@ function LoginScreen() {
|
|
|
655
967
|
|
|
656
968
|
- `@mspkapps/auth-client` is **backend-only**.
|
|
657
969
|
- API key, secret, and `googleClientId` live **only in backend env vars**.
|
|
658
|
-
- Frontend talks to backend over HTTPS (`/api/auth/*`, `/api/user/*`).
|
|
970
|
+
- Frontend talks to backend over HTTPS (`/api/auth/*`, `/api/user/*`, `/api/developer/*`).
|
|
659
971
|
- Frontend stores only **user-level access token** (e.g. in `localStorage` / `AsyncStorage`).
|
|
660
972
|
- Never expose API key/secret in web or mobile bundles.
|
|
973
|
+
- **Developer ID is automatically extracted** from API key/secret by the backend - no need to pass it explicitly.
|
|
661
974
|
|
|
662
975
|
---
|
|
663
976
|
|
|
664
|
-
## 5.
|
|
977
|
+
## 5. API Reference
|
|
978
|
+
|
|
979
|
+
### 5.1 Authentication APIs
|
|
980
|
+
|
|
981
|
+
| Method | Endpoint | Description |
|
|
982
|
+
|--------|----------|-------------|
|
|
983
|
+
| `authclient.register()` | `POST /api/auth/register` | Register new user |
|
|
984
|
+
| `authclient.login()` | `POST /api/auth/login` | Login user |
|
|
985
|
+
| `authclient.googleAuth()` | `POST /api/auth/google` | Google OAuth login |
|
|
986
|
+
| `authclient.verifyToken()` | `POST /api/auth/verify-token` | Verify access token |
|
|
987
|
+
|
|
988
|
+
### 5.2 User Profile APIs
|
|
989
|
+
|
|
990
|
+
| Method | Endpoint | Description |
|
|
991
|
+
|--------|----------|-------------|
|
|
992
|
+
| `authclient.getProfile()` | `GET /api/user/profile` | Get user profile |
|
|
993
|
+
| `authclient.updateProfile()` | `PATCH /api/user/profile` | Update user profile |
|
|
994
|
+
|
|
995
|
+
### 5.3 Developer Data APIs (NEW)
|
|
996
|
+
|
|
997
|
+
| Method | Endpoint | Description |
|
|
998
|
+
|--------|----------|-------------|
|
|
999
|
+
| `authclient.getDeveloperGroups()` | `GET /api/developer/groups` | Get all groups for the authenticated developer |
|
|
1000
|
+
| `authclient.getDeveloperApps()` | `GET /api/developer/apps` | Get all apps (with and without groups) |
|
|
1001
|
+
| `authclient.getDeveloperApps(123)` | `GET /api/developer/apps?group_id=123` | Get apps in specific group |
|
|
1002
|
+
| `authclient.getDeveloperApps(null)` | `GET /api/developer/apps?group_id=null` | Get apps NOT in any group |
|
|
1003
|
+
| `authclient.getAppUsers({ appId, page, limit })` | `GET /api/developer/users?app_id=X&page=Y&limit=Z` | Get users for specific app (paginated) |
|
|
1004
|
+
| `authclient.getUserData(userId)` | `GET /api/developer/user/:user_id` | Get specific user data |
|
|
1005
|
+
|
|
1006
|
+
**Notes:**
|
|
1007
|
+
- All user data responses **exclude the password field** for security
|
|
1008
|
+
- User data includes all custom `extra` fields configured for the app
|
|
1009
|
+
- `developer_id` is **automatically extracted** from API key & secret - you never pass it manually
|
|
1010
|
+
- Pagination is supported for user lists with `page` and `limit` parameters
|
|
1011
|
+
|
|
1012
|
+
---
|
|
1013
|
+
|
|
1014
|
+
## 6. Troubleshooting
|
|
665
1015
|
|
|
666
1016
|
### Frontend gets 4xx/5xx from backend
|
|
667
1017
|
|
|
@@ -673,11 +1023,24 @@ function LoginScreen() {
|
|
|
673
1023
|
- Ensure `GOOGLE_CLIENT_ID` in backend matches the client ID used on the frontend.
|
|
674
1024
|
- Check that the frontend sends `credential` / `id_token` to `/api/auth/google` correctly.
|
|
675
1025
|
|
|
1026
|
+
### Developer data APIs return 403/404
|
|
1027
|
+
|
|
1028
|
+
- Verify that the API key & secret in your backend env vars are correct.
|
|
1029
|
+
- The developer ID is extracted automatically from these credentials.
|
|
1030
|
+
- Ensure you're querying data that belongs to the authenticated developer.
|
|
1031
|
+
|
|
1032
|
+
### App users query returns empty results
|
|
1033
|
+
|
|
1034
|
+
- Verify the `app_id` belongs to the authenticated developer.
|
|
1035
|
+
- Check that the app actually has registered users.
|
|
1036
|
+
|
|
676
1037
|
---
|
|
677
1038
|
|
|
678
|
-
##
|
|
1039
|
+
## 7. Summary
|
|
679
1040
|
|
|
680
1041
|
- Install and initialize `@mspkapps/auth-client` **only in your backend**.
|
|
681
1042
|
- Implement clean REST endpoints in your backend that call `authclient` methods.
|
|
682
1043
|
- React and React Native frontends call those endpoints with plain HTTP (fetch/axios).
|
|
683
1044
|
- This keeps API keys safe and maintains a clean separation between frontend and backend.
|
|
1045
|
+
- Use the new **Developer Data APIs** to build dashboards that display groups, apps, and users.
|
|
1046
|
+
- Developer ID is **automatically extracted** from API credentials - never passed manually.
|
package/package.json
CHANGED
package/src/AuthClient.js
CHANGED
|
@@ -14,7 +14,8 @@ export class AuthClient {
|
|
|
14
14
|
storage,
|
|
15
15
|
fetch: fetchFn,
|
|
16
16
|
keyInPath = true,
|
|
17
|
-
googleClientId = null,
|
|
17
|
+
googleClientId = null,
|
|
18
|
+
developerId = null, // NEW: for developer-level APIs
|
|
18
19
|
} = {}) {
|
|
19
20
|
if (!apiKey) throw new Error('apiKey is required');
|
|
20
21
|
if (!apiSecret) throw new Error('apiSecret is required');
|
|
@@ -22,7 +23,8 @@ export class AuthClient {
|
|
|
22
23
|
this.apiSecret = apiSecret;
|
|
23
24
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
24
25
|
this.keyInPath = !!keyInPath;
|
|
25
|
-
this.googleClientId = googleClientId || null;
|
|
26
|
+
this.googleClientId = googleClientId || null;
|
|
27
|
+
this.developerId = developerId || null; // Store developer ID
|
|
26
28
|
|
|
27
29
|
const f = fetchFn || (typeof window !== 'undefined' ? window.fetch : (typeof fetch !== 'undefined' ? fetch : null));
|
|
28
30
|
if (!f) throw new Error('No fetch available. Pass { fetch } or run on Node 18+/browsers.');
|
|
@@ -52,6 +54,7 @@ export class AuthClient {
|
|
|
52
54
|
'X-API-Key': this.apiKey,
|
|
53
55
|
'X-API-Secret': this.apiSecret,
|
|
54
56
|
...(this.googleClientId ? { 'X-Google-Client-Id': this.googleClientId } : {}),
|
|
57
|
+
...(this.developerId ? { 'X-Developer-Id': this.developerId } : {}),
|
|
55
58
|
...(this.token ? { Authorization: `UserToken ${this.token}` } : {}),
|
|
56
59
|
...extra
|
|
57
60
|
};
|
|
@@ -63,6 +66,10 @@ export class AuthClient {
|
|
|
63
66
|
else this._clear(this.tokenKey);
|
|
64
67
|
}
|
|
65
68
|
|
|
69
|
+
setDeveloperId(developerId) {
|
|
70
|
+
this.developerId = developerId || null;
|
|
71
|
+
}
|
|
72
|
+
|
|
66
73
|
getAuthHeader() { return this.token ? { Authorization: `UserToken ${this.token}` } : {}; }
|
|
67
74
|
logout() { this.setToken(null); }
|
|
68
75
|
|
|
@@ -104,7 +111,6 @@ export class AuthClient {
|
|
|
104
111
|
);
|
|
105
112
|
}
|
|
106
113
|
|
|
107
|
-
// include googleClientId in body too (helpful if backend needs it)
|
|
108
114
|
const body = { id_token };
|
|
109
115
|
if (this.googleClientId) body.google_client_id = this.googleClientId;
|
|
110
116
|
|
|
@@ -234,8 +240,13 @@ export class AuthClient {
|
|
|
234
240
|
}
|
|
235
241
|
|
|
236
242
|
// ---------- Developer Data APIs ----------
|
|
243
|
+
// Note: Requires developerId to be set via constructor or setDeveloperId()
|
|
244
|
+
|
|
237
245
|
async getDeveloperGroups() {
|
|
238
|
-
|
|
246
|
+
if (!this.developerId) {
|
|
247
|
+
throw new AuthError('Developer ID is required. Set it via constructor or setDeveloperId()', 400, 'MISSING_DEVELOPER_ID', null);
|
|
248
|
+
}
|
|
249
|
+
const resp = await this.fetch(`${this.baseUrl}/developer/groups`, {
|
|
239
250
|
method: 'GET',
|
|
240
251
|
headers: this._headers()
|
|
241
252
|
});
|
|
@@ -244,10 +255,35 @@ export class AuthClient {
|
|
|
244
255
|
return json;
|
|
245
256
|
}
|
|
246
257
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
258
|
+
/**
|
|
259
|
+
* Get developer's apps
|
|
260
|
+
* @param {number|string|null} groupId - Optional. Filter by group ID, pass null/'null' for apps without groups, omit for all apps
|
|
261
|
+
* @returns {Promise} API response with app data
|
|
262
|
+
* @example
|
|
263
|
+
* // Get all apps (with and without groups)
|
|
264
|
+
* await client.getDeveloperApps();
|
|
265
|
+
*
|
|
266
|
+
* // Get apps in specific group
|
|
267
|
+
* await client.getDeveloperApps(123);
|
|
268
|
+
*
|
|
269
|
+
* // Get only apps NOT in any group
|
|
270
|
+
* await client.getDeveloperApps(null);
|
|
271
|
+
*/
|
|
272
|
+
async getDeveloperApps(groupId = undefined) {
|
|
273
|
+
if (!this.developerId) {
|
|
274
|
+
throw new AuthError('Developer ID is required. Set it via constructor or setDeveloperId()', 400, 'MISSING_DEVELOPER_ID', null);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let url = `${this.baseUrl}/developer/apps`;
|
|
278
|
+
|
|
279
|
+
if (groupId !== undefined) {
|
|
280
|
+
if (groupId === null || groupId === 'null') {
|
|
281
|
+
url += '?group_id=null';
|
|
282
|
+
} else {
|
|
283
|
+
url += `?group_id=${encodeURIComponent(groupId)}`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
251
287
|
const resp = await this.fetch(url, {
|
|
252
288
|
method: 'GET',
|
|
253
289
|
headers: this._headers()
|
|
@@ -258,8 +294,11 @@ export class AuthClient {
|
|
|
258
294
|
}
|
|
259
295
|
|
|
260
296
|
async getAppUsers({ appId, page = 1, limit = 50 }) {
|
|
297
|
+
if (!this.developerId) {
|
|
298
|
+
throw new AuthError('Developer ID is required. Set it via constructor or setDeveloperId()', 400, 'MISSING_DEVELOPER_ID', null);
|
|
299
|
+
}
|
|
261
300
|
if (!appId) throw new AuthError('appId is required', 400, 'MISSING_APP_ID', null);
|
|
262
|
-
const url = this.
|
|
301
|
+
const url = `${this.baseUrl}/developer/users?app_id=${encodeURIComponent(appId)}&page=${page}&limit=${limit}`;
|
|
263
302
|
const resp = await this.fetch(url, {
|
|
264
303
|
method: 'GET',
|
|
265
304
|
headers: this._headers()
|
|
@@ -270,8 +309,11 @@ export class AuthClient {
|
|
|
270
309
|
}
|
|
271
310
|
|
|
272
311
|
async getUserData(userId) {
|
|
312
|
+
if (!this.developerId) {
|
|
313
|
+
throw new AuthError('Developer ID is required. Set it via constructor or setDeveloperId()', 400, 'MISSING_DEVELOPER_ID', null);
|
|
314
|
+
}
|
|
273
315
|
if (!userId) throw new AuthError('userId is required', 400, 'MISSING_USER_ID', null);
|
|
274
|
-
const resp = await this.fetch(this.
|
|
316
|
+
const resp = await this.fetch(`${this.baseUrl}/developer/user/${encodeURIComponent(userId)}`, {
|
|
275
317
|
method: 'GET',
|
|
276
318
|
headers: this._headers()
|
|
277
319
|
});
|
|
@@ -296,7 +338,6 @@ function toError(resp, json, fallback) {
|
|
|
296
338
|
|
|
297
339
|
// ---- Singleton-style convenience API ----
|
|
298
340
|
|
|
299
|
-
// Internal holder
|
|
300
341
|
const _singleton = { client: null };
|
|
301
342
|
|
|
302
343
|
function ensureClient() {
|
|
@@ -308,13 +349,13 @@ function ensureClient() {
|
|
|
308
349
|
return _singleton.client;
|
|
309
350
|
}
|
|
310
351
|
|
|
311
|
-
// Initialize once in your backend (typically at startup)
|
|
312
352
|
function init({
|
|
313
353
|
apiKey = process.env.MSPK_AUTH_API_KEY,
|
|
314
354
|
apiSecret = process.env.MSPK_AUTH_API_SECRET,
|
|
315
355
|
googleClientId = process.env.GOOGLE_CLIENT_ID,
|
|
316
|
-
|
|
317
|
-
|
|
356
|
+
developerId = process.env.MSPK_DEVELOPER_ID, // NEW
|
|
357
|
+
baseUrl,
|
|
358
|
+
storage,
|
|
318
359
|
fetch: fetchFn,
|
|
319
360
|
keyInPath,
|
|
320
361
|
} = {}) {
|
|
@@ -322,6 +363,7 @@ function init({
|
|
|
322
363
|
apiKey,
|
|
323
364
|
apiSecret,
|
|
324
365
|
googleClientId,
|
|
366
|
+
developerId,
|
|
325
367
|
baseUrl,
|
|
326
368
|
storage,
|
|
327
369
|
fetch: fetchFn,
|
|
@@ -330,7 +372,6 @@ function init({
|
|
|
330
372
|
return _singleton.client;
|
|
331
373
|
}
|
|
332
374
|
|
|
333
|
-
// Thin wrappers delegating to the singleton
|
|
334
375
|
const authclient = {
|
|
335
376
|
init,
|
|
336
377
|
get client() {
|
|
@@ -372,11 +413,16 @@ const authclient = {
|
|
|
372
413
|
return ensureClient().verifyToken(accessToken);
|
|
373
414
|
},
|
|
374
415
|
|
|
375
|
-
// developer
|
|
416
|
+
// developer ID management
|
|
417
|
+
setDeveloperId(developerId) {
|
|
418
|
+
return ensureClient().setDeveloperId(developerId);
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
// developer data APIs (requires developerId to be set)
|
|
376
422
|
getDeveloperGroups() {
|
|
377
423
|
return ensureClient().getDeveloperGroups();
|
|
378
424
|
},
|
|
379
|
-
getDeveloperApps(groupId =
|
|
425
|
+
getDeveloperApps(groupId = undefined) {
|
|
380
426
|
return ensureClient().getDeveloperApps(groupId);
|
|
381
427
|
},
|
|
382
428
|
getAppUsers({ appId, page, limit }) {
|
|
@@ -387,5 +433,5 @@ const authclient = {
|
|
|
387
433
|
},
|
|
388
434
|
};
|
|
389
435
|
|
|
390
|
-
export { authclient, init };
|
|
391
|
-
export default authclient;
|
|
436
|
+
export { authclient, init };
|
|
437
|
+
export default authclient;
|