@mspkapps/auth-client 0.1.28 → 0.1.31
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 +92 -10
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,7 @@ export class AuthClient {
|
|
|
14
14
|
storage,
|
|
15
15
|
fetch: fetchFn,
|
|
16
16
|
keyInPath = true,
|
|
17
|
-
googleClientId = null,
|
|
17
|
+
googleClientId = null,
|
|
18
18
|
} = {}) {
|
|
19
19
|
if (!apiKey) throw new Error('apiKey is required');
|
|
20
20
|
if (!apiSecret) throw new Error('apiSecret is required');
|
|
@@ -22,7 +22,7 @@ export class AuthClient {
|
|
|
22
22
|
this.apiSecret = apiSecret;
|
|
23
23
|
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
24
24
|
this.keyInPath = !!keyInPath;
|
|
25
|
-
this.googleClientId = googleClientId || null;
|
|
25
|
+
this.googleClientId = googleClientId || null;
|
|
26
26
|
|
|
27
27
|
const f = fetchFn || (typeof window !== 'undefined' ? window.fetch : (typeof fetch !== 'undefined' ? fetch : null));
|
|
28
28
|
if (!f) throw new Error('No fetch available. Pass { fetch } or run on Node 18+/browsers.');
|
|
@@ -104,7 +104,6 @@ export class AuthClient {
|
|
|
104
104
|
);
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
// include googleClientId in body too (helpful if backend needs it)
|
|
108
107
|
const body = { id_token };
|
|
109
108
|
if (this.googleClientId) body.google_client_id = this.googleClientId;
|
|
110
109
|
|
|
@@ -232,6 +231,78 @@ export class AuthClient {
|
|
|
232
231
|
if (!resp.ok || json?.success === false) throw toError(resp, json, 'Request failed');
|
|
233
232
|
return json;
|
|
234
233
|
}
|
|
234
|
+
|
|
235
|
+
// ---------- Developer Data APIs ----------
|
|
236
|
+
// Note: developer_id is automatically extracted from API key & secret on the backend
|
|
237
|
+
|
|
238
|
+
async getDeveloperGroups() {
|
|
239
|
+
const resp = await this.fetch(this._buildUrl('developer/groups'), {
|
|
240
|
+
method: 'GET',
|
|
241
|
+
headers: this._headers()
|
|
242
|
+
});
|
|
243
|
+
const json = await safeJson(resp);
|
|
244
|
+
if (!resp.ok || json?.success === false) throw toError(resp, json, 'Get developer groups failed');
|
|
245
|
+
return json;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Get developer's apps
|
|
250
|
+
* @param {number|string|null} groupId - Optional. Filter by group ID, pass null/'null' for apps without groups, omit for all apps
|
|
251
|
+
* @returns {Promise} API response with app data
|
|
252
|
+
* @example
|
|
253
|
+
* // Get all apps (with and without groups)
|
|
254
|
+
* await client.getDeveloperApps();
|
|
255
|
+
*
|
|
256
|
+
* // Get apps in specific group
|
|
257
|
+
* await client.getDeveloperApps(123);
|
|
258
|
+
*
|
|
259
|
+
* // Get only apps NOT in any group
|
|
260
|
+
* await client.getDeveloperApps(null);
|
|
261
|
+
*/
|
|
262
|
+
async getDeveloperApps(groupId = undefined) {
|
|
263
|
+
let url = this._buildUrl('developer/apps');
|
|
264
|
+
|
|
265
|
+
if (groupId !== undefined) {
|
|
266
|
+
if (groupId === null || groupId === 'null') {
|
|
267
|
+
// Get apps without groups
|
|
268
|
+
url += '?group_id=null';
|
|
269
|
+
} else {
|
|
270
|
+
// Get apps for specific group
|
|
271
|
+
url += `?group_id=${encodeURIComponent(groupId)}`;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const resp = await this.fetch(url, {
|
|
276
|
+
method: 'GET',
|
|
277
|
+
headers: this._headers()
|
|
278
|
+
});
|
|
279
|
+
const json = await safeJson(resp);
|
|
280
|
+
if (!resp.ok || json?.success === false) throw toError(resp, json, 'Get developer apps failed');
|
|
281
|
+
return json;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
async getAppUsers({ appId, page = 1, limit = 50 }) {
|
|
285
|
+
if (!appId) throw new AuthError('appId is required', 400, 'MISSING_APP_ID', null);
|
|
286
|
+
const url = this._buildUrl(`developer/users?app_id=${encodeURIComponent(appId)}&page=${page}&limit=${limit}`);
|
|
287
|
+
const resp = await this.fetch(url, {
|
|
288
|
+
method: 'GET',
|
|
289
|
+
headers: this._headers()
|
|
290
|
+
});
|
|
291
|
+
const json = await safeJson(resp);
|
|
292
|
+
if (!resp.ok || json?.success === false) throw toError(resp, json, 'Get app users failed');
|
|
293
|
+
return json;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async getUserData(userId) {
|
|
297
|
+
if (!userId) throw new AuthError('userId is required', 400, 'MISSING_USER_ID', null);
|
|
298
|
+
const resp = await this.fetch(this._buildUrl(`developer/user/${encodeURIComponent(userId)}`), {
|
|
299
|
+
method: 'GET',
|
|
300
|
+
headers: this._headers()
|
|
301
|
+
});
|
|
302
|
+
const json = await safeJson(resp);
|
|
303
|
+
if (!resp.ok || json?.success === false) throw toError(resp, json, 'Get user data failed');
|
|
304
|
+
return json;
|
|
305
|
+
}
|
|
235
306
|
}
|
|
236
307
|
|
|
237
308
|
// ---------- helpers ----------
|
|
@@ -249,7 +320,6 @@ function toError(resp, json, fallback) {
|
|
|
249
320
|
|
|
250
321
|
// ---- Singleton-style convenience API ----
|
|
251
322
|
|
|
252
|
-
// Internal holder
|
|
253
323
|
const _singleton = { client: null };
|
|
254
324
|
|
|
255
325
|
function ensureClient() {
|
|
@@ -261,13 +331,12 @@ function ensureClient() {
|
|
|
261
331
|
return _singleton.client;
|
|
262
332
|
}
|
|
263
333
|
|
|
264
|
-
// Initialize once in your backend (typically at startup)
|
|
265
334
|
function init({
|
|
266
335
|
apiKey = process.env.MSPK_AUTH_API_KEY,
|
|
267
336
|
apiSecret = process.env.MSPK_AUTH_API_SECRET,
|
|
268
337
|
googleClientId = process.env.GOOGLE_CLIENT_ID,
|
|
269
|
-
baseUrl,
|
|
270
|
-
storage,
|
|
338
|
+
baseUrl,
|
|
339
|
+
storage,
|
|
271
340
|
fetch: fetchFn,
|
|
272
341
|
keyInPath,
|
|
273
342
|
} = {}) {
|
|
@@ -283,7 +352,6 @@ function init({
|
|
|
283
352
|
return _singleton.client;
|
|
284
353
|
}
|
|
285
354
|
|
|
286
|
-
// Thin wrappers delegating to the singleton
|
|
287
355
|
const authclient = {
|
|
288
356
|
init,
|
|
289
357
|
get client() {
|
|
@@ -324,7 +392,21 @@ const authclient = {
|
|
|
324
392
|
verifyToken(accessToken) {
|
|
325
393
|
return ensureClient().verifyToken(accessToken);
|
|
326
394
|
},
|
|
395
|
+
|
|
396
|
+
// developer data APIs (dev_id extracted automatically from API credentials)
|
|
397
|
+
getDeveloperGroups() {
|
|
398
|
+
return ensureClient().getDeveloperGroups();
|
|
399
|
+
},
|
|
400
|
+
getDeveloperApps(groupId = undefined) {
|
|
401
|
+
return ensureClient().getDeveloperApps(groupId);
|
|
402
|
+
},
|
|
403
|
+
getAppUsers({ appId, page, limit }) {
|
|
404
|
+
return ensureClient().getAppUsers({ appId, page, limit });
|
|
405
|
+
},
|
|
406
|
+
getUserData(userId) {
|
|
407
|
+
return ensureClient().getUserData(userId);
|
|
408
|
+
},
|
|
327
409
|
};
|
|
328
410
|
|
|
329
|
-
export { authclient, init };
|
|
330
|
-
export default authclient;
|
|
411
|
+
export { authclient, init };
|
|
412
|
+
export default authclient;
|