@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 CHANGED
@@ -232,7 +232,160 @@ router.patch('/profile', requireAuth, async (req, res) => {
232
232
  export default router;
233
233
  ```
234
234
 
235
- ### 1.6 Wire Up Routes in Backend App
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. Troubleshooting
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
- ## 6. Summary
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mspkapps/auth-client",
3
- "version": "0.1.29",
3
+ "version": "0.1.32",
4
4
  "description": "Lightweight client for Your Auth Service",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
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, // new option
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; // store it
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
- const resp = await this.fetch(this._buildUrl('developer/groups'), {
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
- async getDeveloperApps(groupId = null) {
248
- const url = groupId
249
- ? this._buildUrl(`developer/apps?group_id=${encodeURIComponent(groupId)}`)
250
- : this._buildUrl('developer/apps');
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._buildUrl(`developer/users?app_id=${encodeURIComponent(appId)}&page=${page}&limit=${limit}`);
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._buildUrl(`developer/user/${encodeURIComponent(userId)}`), {
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
- baseUrl, // optional override
317
- storage, // usually omit on backend
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 data APIs
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 = null) {
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 }; // named exports if someone prefers them
391
- export default authclient; // default export for your desired DX
436
+ export { authclient, init };
437
+ export default authclient;