@amaster.ai/auth-client 1.1.0-beta.7 → 1.1.0-beta.71

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
@@ -8,6 +8,7 @@ Authentication SDK for Amaster Platform - Complete client-side authentication so
8
8
  - 🔄 **Automatic Token Refresh**: JWT token auto-refresh before expiration
9
9
  - 📦 **Token Storage**: localStorage or sessionStorage with SSR support
10
10
  - 🎯 **Permission System**: Role-based and permission-based access control
11
+ - 👤 **Anonymous Access**: Automatic support for anonymous users with configurable permissions
11
12
  - 🔗 **OAuth Integration**: Google, GitHub, WeChat OAuth, WeChat Mini Program, and custom OAuth providers
12
13
  - 📡 **Event System**: Listen to login, logout, and token events
13
14
  - 💾 **Session Management**: View and revoke active sessions
@@ -58,7 +59,7 @@ if (result.data) {
58
59
  const user = await authClient.getMe();
59
60
 
60
61
  // Check permissions
61
- if (authClient.hasPermission("user.read")) {
62
+ if (authClient.hasPermission("user", "read")) {
62
63
  // Show user list
63
64
  }
64
65
  ```
@@ -82,10 +83,10 @@ const authClient = createAuthClient();
82
83
 
83
84
  ```typescript
84
85
  interface AuthClientOptions {
85
- baseURL?: string; // API base URL, defaults to window.location.origin
86
+ baseURL?: string; // API base URL, defaults to window.location.origin
86
87
  headers?: Record<string, string>; // Custom headers for all requests
87
- onTokenExpired?: () => void; // Token expiration callback
88
- onUnauthorized?: () => void; // Unauthorized (401) callback
88
+ onTokenExpired?: () => void; // Token expiration callback
89
+ onUnauthorized?: () => void; // Unauthorized (401) callback
89
90
  }
90
91
  ```
91
92
 
@@ -94,19 +95,18 @@ interface AuthClientOptions {
94
95
  ```typescript
95
96
  const authClient = createAuthClient({
96
97
  baseURL: "https://api.example.com",
97
- onTokenExpired: () => window.location.href = "/login",
98
+ onTokenExpired: () => (window.location.href = "/login"),
98
99
  onUnauthorized: () => alert("Session expired"),
99
100
  });
100
101
  ```
101
102
 
102
103
  **Built-in Features (Zero Config):**
103
104
 
104
- - ✅ **Auto Environment Detection**: Works in browser, WeChat Mini Program, SSR
105
- - ✅ **Auto Token Refresh**: Refreshes 5 minutes before expiry
106
- - ✅ **Auto Permission Sync**: Syncs on page load and every token refresh
107
- - ✅ **Smart Storage**: localStorage (browser), wx.storage (WeChat), no-op (SSR)
108
- - ✅ **Lightweight Data**: Only permission names cached, not full objects
109
- - ✅ **On-Demand DataScope**: Loads only when needed
105
+ - ✅ Auto environment detection (browser, WeChat Mini Program, SSR)
106
+ - ✅ Auto token refresh (5 minutes before expiry)
107
+ - ✅ Auto permission sync
108
+ - ✅ Auto anonymous support
109
+ - ✅ Smart storage (localStorage, wx.storage, no-op for SSR)
110
110
 
111
111
  ## Authentication
112
112
 
@@ -180,15 +180,16 @@ const redirectUrl = sessionStorage.getItem("oauth_redirect") || "/";
180
180
  window.location.href = redirectUrl;
181
181
  ```
182
182
 
183
+ If the callback page URL already carries `?redirect=...` such as
184
+ `/login?redirect=%2Fapi%2Foauth%2Fauthorize...`, the SDK will reuse that
185
+ redirect target automatically after a successful OAuth callback. Both
186
+ `#access_token=...` and `#accessToken=...` callback hash formats are supported.
187
+
183
188
  **Popup window:**
184
189
 
185
190
  ```typescript
186
191
  // Main page
187
- const popup = window.open(
188
- "/api/auth/oauth/google",
189
- "oauth-login",
190
- "width=600,height=700"
191
- );
192
+ const popup = window.open("/api/auth/oauth/google", "oauth-login", "width=600,height=700");
192
193
 
193
194
  window.addEventListener("message", (event) => {
194
195
  if (event.data.type === "oauth-success") {
@@ -213,7 +214,7 @@ import { createAuthClient } from "@amaster.ai/auth-client";
213
214
 
214
215
  // Zero configuration - auto-detects WeChat environment
215
216
  const authClient = createAuthClient({
216
- baseURL: "https://api.yourdomain.com"
217
+ baseURL: "https://api.yourdomain.com",
217
218
  });
218
219
 
219
220
  // Login with WeChat code
@@ -221,22 +222,22 @@ wx.login({
221
222
  success: async (res) => {
222
223
  if (res.code) {
223
224
  const result = await authClient.loginWithMiniProgram(res.code);
224
-
225
+
225
226
  if (result.data) {
226
227
  console.log("Logged in:", result.data.user);
227
228
  // Token automatically saved to wx.storage
228
- wx.switchTab({ url: '/pages/index/index' });
229
+ wx.switchTab({ url: "/pages/index/index" });
229
230
  } else if (result.error) {
230
231
  wx.showToast({
231
- title: result.error.message || 'Login failed',
232
- icon: 'none'
232
+ title: result.error.message || "Login failed",
233
+ icon: "none",
233
234
  });
234
235
  }
235
236
  }
236
237
  },
237
238
  fail: (err) => {
238
239
  console.error("wx.login failed:", err);
239
- }
240
+ },
240
241
  });
241
242
  ```
242
243
 
@@ -244,8 +245,8 @@ wx.login({
244
245
 
245
246
  ```xml
246
247
  <!-- WXML -->
247
- <button
248
- open-type="getPhoneNumber"
248
+ <button
249
+ open-type="getPhoneNumber"
249
250
  bindgetphonenumber="onGetPhoneNumber"
250
251
  >
251
252
  Get Phone Number
@@ -256,14 +257,14 @@ wx.login({
256
257
  // JS
257
258
  async onGetPhoneNumber(e) {
258
259
  const { code } = e.detail;
259
-
260
+
260
261
  if (code) {
261
262
  const result = await authClient.getMiniProgramPhoneNumber(code);
262
-
263
+
263
264
  if (result.data) {
264
265
  console.log("Phone number:", result.data.phone);
265
266
  console.log("Verified:", result.data.phoneVerified);
266
-
267
+
267
268
  // Update UI with phone number
268
269
  this.setData({
269
270
  phone: result.data.phone
@@ -288,13 +289,13 @@ async onGetPhoneNumber(e) {
288
289
  import { createAuthClient } from "@amaster.ai/auth-client";
289
290
 
290
291
  const authClient = createAuthClient({
291
- baseURL: "https://api.yourdomain.com"
292
+ baseURL: "https://api.yourdomain.com",
292
293
  });
293
294
 
294
295
  Page({
295
296
  data: {
296
297
  userInfo: null,
297
- hasPhone: false
298
+ hasPhone: false,
298
299
  },
299
300
 
300
301
  // Auto-login on page load
@@ -304,26 +305,26 @@ Page({
304
305
 
305
306
  // WeChat Mini Program login
306
307
  async handleLogin() {
307
- wx.showLoading({ title: 'Logging in...' });
308
-
308
+ wx.showLoading({ title: "Logging in..." });
309
+
309
310
  wx.login({
310
311
  success: async (res) => {
311
312
  if (res.code) {
312
313
  const result = await authClient.loginWithMiniProgram(res.code);
313
-
314
+
314
315
  wx.hideLoading();
315
-
316
+
316
317
  if (result.data) {
317
318
  this.setData({
318
- userInfo: result.data.user
319
+ userInfo: result.data.user,
319
320
  });
320
-
321
+
321
322
  // Navigate to home
322
- wx.switchTab({ url: '/pages/index/index' });
323
+ wx.switchTab({ url: "/pages/index/index" });
323
324
  } else {
324
325
  wx.showToast({
325
- title: 'Login failed',
326
- icon: 'none'
326
+ title: "Login failed",
327
+ icon: "none",
327
328
  });
328
329
  }
329
330
  }
@@ -331,47 +332,47 @@ Page({
331
332
  fail: () => {
332
333
  wx.hideLoading();
333
334
  wx.showToast({
334
- title: 'Login failed',
335
- icon: 'none'
335
+ title: "Login failed",
336
+ icon: "none",
336
337
  });
337
- }
338
+ },
338
339
  });
339
340
  },
340
341
 
341
342
  // Get phone number with user authorization
342
343
  async onGetPhoneNumber(e) {
343
344
  const { code } = e.detail;
344
-
345
+
345
346
  if (!code) {
346
347
  wx.showToast({
347
- title: 'Authorization cancelled',
348
- icon: 'none'
348
+ title: "Authorization cancelled",
349
+ icon: "none",
349
350
  });
350
351
  return;
351
352
  }
352
353
 
353
- wx.showLoading({ title: 'Getting phone...' });
354
-
354
+ wx.showLoading({ title: "Getting phone..." });
355
+
355
356
  const result = await authClient.getMiniProgramPhoneNumber(code);
356
-
357
+
357
358
  wx.hideLoading();
358
-
359
+
359
360
  if (result.data) {
360
361
  this.setData({
361
- hasPhone: true
362
+ hasPhone: true,
362
363
  });
363
-
364
+
364
365
  wx.showToast({
365
- title: 'Phone number obtained',
366
- icon: 'success'
366
+ title: "Phone number obtained",
367
+ icon: "success",
367
368
  });
368
369
  } else {
369
370
  wx.showToast({
370
- title: result.error?.message || 'Failed to get phone',
371
- icon: 'none'
371
+ title: result.error?.message || "Failed to get phone",
372
+ icon: "none",
372
373
  });
373
374
  }
374
- }
375
+ },
375
376
  });
376
377
  ```
377
378
 
@@ -412,67 +413,50 @@ await authClient.changePassword({
412
413
 
413
414
  ## Permission Checks
414
415
 
415
- ### Local Permission Checks (Fast)
416
+ ### Anonymous Access Support
416
417
 
417
- Permissions are cached locally as string arrays for fast UI checks:
418
+ The SDK automatically supports anonymous users with **zero configuration**:
418
419
 
419
420
  ```typescript
420
- // Check single role
421
- if (authClient.hasRole("admin")) {
422
- // Admin only
423
- }
421
+ // Create client
422
+ const authClient = createAuthClient();
424
423
 
425
- // Check single permission
426
- if (authClient.hasPermission("user.read")) {
427
- // Can read users
424
+ // Permission checks work for both authenticated and anonymous users
425
+ if (authClient.hasPermission("article", "read")) {
426
+ showArticleList();
428
427
  }
429
428
 
430
- // Check any permission
431
- if (authClient.hasAnyPermission(["user.read", "user.manage"])) {
432
- // Has at least one permission
429
+ // Check if user is anonymous
430
+ if (authClient.isAnonymous()) {
431
+ showLoginPrompt();
433
432
  }
434
433
 
435
- // Check all permissions
436
- if (authClient.hasAllPermissions(["user.read", "user.write"])) {
437
- // Has all permissions
438
- }
434
+ // After login, permissions automatically update
435
+ await authClient.login({ ... });
439
436
  ```
440
437
 
441
- ### On-Demand Data Scope Loading
438
+ ### Local Permission Checks (Fast)
442
439
 
443
- Data scopes are loaded only when needed to reduce data transfer:
440
+ Permissions are cached locally for fast UI checks:
444
441
 
445
442
  ```typescript
446
- // Get data scope for a specific permission
447
- const result = await authClient.getPermissionScope("user.read");
448
- if (result.data) {
449
- const { dataScope } = result.data;
450
-
451
- if (dataScope.scopeType === "department") {
452
- // Filter users by department
453
- const users = await api.getUsers({
454
- departmentId: dataScope.scopeFilter.departmentId
455
- });
456
- } else if (dataScope.scopeType === "all") {
457
- // Show all users
458
- const users = await api.getUsers();
459
- }
443
+ // Check role
444
+ if (authClient.hasRole("admin")) {
445
+ showAdminPanel();
460
446
  }
461
- ```
462
447
 
463
- **Optimized Data Structure:**
448
+ if (authClient.isAnonymous()) {
449
+ showLoginPrompt();
450
+ }
464
451
 
465
- ```typescript
466
- // User object (lightweight)
467
- {
468
- uid: "xxx",
469
- email: "user@example.com",
470
- roles: ["admin", "user"], // Only role codes
471
- permissions: ["user.read", "user.write", "order.read"], // Only permission names
472
- // NO dataScopes - loaded on demand
452
+ // Check permission
453
+ if (authClient.hasPermission("user", "read")) {
454
+ showUserList();
473
455
  }
474
456
  ```
475
457
 
458
+ ````
459
+
476
460
  ## OAuth Bindings
477
461
 
478
462
  ### Get Bindings
@@ -613,48 +597,32 @@ if (result.error) {
613
597
 
614
598
  ### Token Management
615
599
 
616
- - SDK automatically manages Access Token and Refresh Token
617
- - Access Token is stored in localStorage/sessionStorage
618
- - Refresh Token is managed by backend via HttpOnly Cookie
619
- - Token is automatically refreshed 5 minutes before expiration (configurable)
620
-
621
- ### Permission Sync
622
-
623
- - **On Page Load**: User info and permissions automatically sync from backend
624
- - **On Token Refresh**: Permissions re-sync every 5 minutes when token refreshes
625
- - **Manual Sync**: Call `await authClient.getMe()` to force sync anytime
626
-
627
- **Behavior:**
628
- - Permissions always stay fresh without any configuration
629
- - Page refresh loads latest permissions from backend
630
- - Background sync happens automatically every 5 minutes
600
+ - Tokens are automatically managed by the SDK
601
+ - Access Token refreshes 5 minutes before expiration
602
+ - No manual token handling required
631
603
 
632
604
  ### Permission Checks
633
605
 
634
- - **Frontend permission checks are for UI control only** (show/hide buttons, menus)
635
- - **Backend must verify permissions again** - frontend checks are NOT security measures
636
- - Use permission checks to improve UX, not as security enforcement
606
+ - Use permission checks for UI control (show/hide buttons, menus)
607
+ - Frontend checks are NOT security measures
608
+ - Backend must always verify permissions
637
609
 
638
610
  ### SSR Support
639
611
 
640
- The SDK detects SSR environments and uses a no-op storage adapter:
612
+ The SDK works in both browser and SSR environments:
641
613
 
642
614
  ```typescript
643
- // Safe in both browser and SSR
644
615
  const authClient = createAuthClient();
645
616
  ```
646
617
 
647
618
  ## TypeScript
648
619
 
649
- Full TypeScript support with comprehensive type definitions:
620
+ Full TypeScript support:
650
621
 
651
622
  ```typescript
652
623
  import type {
653
624
  User,
654
625
  LoginResponse,
655
- Permission,
656
- Role,
657
- DataScope,
658
626
  Session,
659
627
  OAuthBinding,
660
628
  } from "@amaster.ai/auth-client";
@@ -667,3 +635,4 @@ MIT
667
635
  ## Contributing
668
636
 
669
637
  Contributions are welcome! Please read our contributing guidelines before submitting PRs.
638
+ ````
package/dist/auth.d.cts CHANGED
@@ -14,7 +14,7 @@
14
14
  * ============================================================================
15
15
  */
16
16
  import { HttpClient, ClientResult } from '@amaster.ai/http-client';
17
- import { q as User, i as RegisterParams, e as LoginResponse, L as LoginParams, c as CodeLoginParams, S as SendCodeParams, p as SuccessResponse, b as CaptchaResponse, g as OAuthProvider, M as MiniProgramPhoneResponse, R as RefreshTokenResponse } from './types-C56GAJKY.cjs';
17
+ import { q as User, i as RegisterParams, e as LoginResponse, L as LoginParams, c as CodeLoginParams, S as SendCodeParams, p as SuccessResponse, b as CaptchaResponse, g as OAuthProvider, M as MiniProgramPhoneResponse, R as RefreshTokenResponse } from './types-B76rOTYH.cjs';
18
18
 
19
19
  /**
20
20
  * Authentication Module
@@ -30,11 +30,15 @@ import { q as User, i as RegisterParams, e as LoginResponse, L as LoginParams, c
30
30
  * - Logout and token refresh
31
31
  */
32
32
 
33
+ declare const browserNavigation: {
34
+ replace(url: string): void;
35
+ };
33
36
  interface AuthModuleDeps {
34
37
  http: HttpClient;
35
38
  onLoginSuccess: (user: User, accessToken: string) => void;
36
39
  storage: {
37
40
  getItem: (key: string) => string | null;
41
+ setItem: (key: string, value: string) => void;
38
42
  };
39
43
  clearAuth: () => void;
40
44
  }
@@ -59,8 +63,14 @@ declare function createAuthModule(deps: AuthModuleDeps): {
59
63
  * @category Authentication
60
64
  * @example
61
65
  * ```typescript
66
+ * // Auto-detect loginType from username
67
+ * await auth.login({
68
+ * username: "john_doe",
69
+ * password: "Password@123",
70
+ * });
71
+ *
72
+ * // Auto-detect loginType from email
62
73
  * await auth.login({
63
- * loginType: "email",
64
74
  * email: "user@example.com",
65
75
  * password: "Password@123",
66
76
  * });
@@ -73,11 +83,17 @@ declare function createAuthModule(deps: AuthModuleDeps): {
73
83
  * @category Authentication
74
84
  * @example
75
85
  * ```typescript
86
+ * // Auto-detect loginType from email
76
87
  * await auth.loginWithCode({
77
- * loginType: "email",
78
88
  * email: "user@example.com",
79
89
  * code: "123456",
80
90
  * });
91
+ *
92
+ * // Auto-detect loginType from phone
93
+ * await auth.loginWithCode({
94
+ * phone: "13800138000",
95
+ * code: "123456",
96
+ * });
81
97
  * ```
82
98
  */
83
99
  loginWithCode(params: CodeLoginParams): Promise<ClientResult<LoginResponse>>;
@@ -180,4 +196,4 @@ declare function createAuthModule(deps: AuthModuleDeps): {
180
196
  };
181
197
  type AuthModule = ReturnType<typeof createAuthModule>;
182
198
 
183
- export { type AuthModule, type AuthModuleDeps, createAuthModule };
199
+ export { type AuthModule, type AuthModuleDeps, browserNavigation, createAuthModule };
package/dist/auth.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * ============================================================================
15
15
  */
16
16
  import { HttpClient, ClientResult } from '@amaster.ai/http-client';
17
- import { q as User, i as RegisterParams, e as LoginResponse, L as LoginParams, c as CodeLoginParams, S as SendCodeParams, p as SuccessResponse, b as CaptchaResponse, g as OAuthProvider, M as MiniProgramPhoneResponse, R as RefreshTokenResponse } from './types-C56GAJKY.js';
17
+ import { q as User, i as RegisterParams, e as LoginResponse, L as LoginParams, c as CodeLoginParams, S as SendCodeParams, p as SuccessResponse, b as CaptchaResponse, g as OAuthProvider, M as MiniProgramPhoneResponse, R as RefreshTokenResponse } from './types-B76rOTYH.js';
18
18
 
19
19
  /**
20
20
  * Authentication Module
@@ -30,11 +30,15 @@ import { q as User, i as RegisterParams, e as LoginResponse, L as LoginParams, c
30
30
  * - Logout and token refresh
31
31
  */
32
32
 
33
+ declare const browserNavigation: {
34
+ replace(url: string): void;
35
+ };
33
36
  interface AuthModuleDeps {
34
37
  http: HttpClient;
35
38
  onLoginSuccess: (user: User, accessToken: string) => void;
36
39
  storage: {
37
40
  getItem: (key: string) => string | null;
41
+ setItem: (key: string, value: string) => void;
38
42
  };
39
43
  clearAuth: () => void;
40
44
  }
@@ -59,8 +63,14 @@ declare function createAuthModule(deps: AuthModuleDeps): {
59
63
  * @category Authentication
60
64
  * @example
61
65
  * ```typescript
66
+ * // Auto-detect loginType from username
67
+ * await auth.login({
68
+ * username: "john_doe",
69
+ * password: "Password@123",
70
+ * });
71
+ *
72
+ * // Auto-detect loginType from email
62
73
  * await auth.login({
63
- * loginType: "email",
64
74
  * email: "user@example.com",
65
75
  * password: "Password@123",
66
76
  * });
@@ -73,11 +83,17 @@ declare function createAuthModule(deps: AuthModuleDeps): {
73
83
  * @category Authentication
74
84
  * @example
75
85
  * ```typescript
86
+ * // Auto-detect loginType from email
76
87
  * await auth.loginWithCode({
77
- * loginType: "email",
78
88
  * email: "user@example.com",
79
89
  * code: "123456",
80
90
  * });
91
+ *
92
+ * // Auto-detect loginType from phone
93
+ * await auth.loginWithCode({
94
+ * phone: "13800138000",
95
+ * code: "123456",
96
+ * });
81
97
  * ```
82
98
  */
83
99
  loginWithCode(params: CodeLoginParams): Promise<ClientResult<LoginResponse>>;
@@ -180,4 +196,4 @@ declare function createAuthModule(deps: AuthModuleDeps): {
180
196
  };
181
197
  type AuthModule = ReturnType<typeof createAuthModule>;
182
198
 
183
- export { type AuthModule, type AuthModuleDeps, createAuthModule };
199
+ export { type AuthModule, type AuthModuleDeps, browserNavigation, createAuthModule };
package/dist/index.cjs CHANGED
@@ -1,2 +1,2 @@
1
- 'use strict';var httpClient=require('@amaster.ai/http-client');var y=class{constructor(){this.events={};}on(e,r){this.events[e]||(this.events[e]=[]),this.events[e].push(r);}off(e,r){this.events[e]&&(this.events[e]=this.events[e].filter(t=>t!==r));}emit(e,...r){this.events[e]&&this.events[e].forEach(t=>{try{t(...r);}catch(a){console.error(`[AuthClient] Error in event handler for "${e}":`,a);}});}removeAllListeners(){this.events={};}};var l={ACCESS_TOKEN:"amaster_access_token",REFRESH_TOKEN:"amaster_refresh_token",USER:"amaster_user"};function N(){return typeof wx<"u"&&wx.getStorageSync?"wechat-miniprogram":typeof window<"u"&&typeof window.localStorage<"u"?"browser":"node"}function S(){switch(N()){case "wechat-miniprogram":return $();case "browser":return L();case "node":return q()}}function L(){let s=window.localStorage;return {getItem(e){try{return s.getItem(e)}catch(r){return console.error("[AuthClient] Failed to get item from localStorage:",r),null}},setItem(e,r){try{s.setItem(e,r);}catch(t){console.error("[AuthClient] Failed to set item in localStorage:",t);}},removeItem(e){try{s.removeItem(e);}catch(r){console.error("[AuthClient] Failed to remove item from localStorage:",r);}},clear(){try{s.removeItem(l.ACCESS_TOKEN),s.removeItem(l.REFRESH_TOKEN),s.removeItem(l.USER);}catch(e){console.error("[AuthClient] Failed to clear localStorage:",e);}}}}function $(){return {getItem(s){try{return wx.getStorageSync(s)||null}catch(e){return console.error("[AuthClient] Failed to get item from WeChat storage:",e),null}},setItem(s,e){try{wx.setStorageSync(s,e);}catch(r){console.error("[AuthClient] Failed to set item in WeChat storage:",r);}},removeItem(s){try{wx.removeStorageSync(s);}catch(e){console.error("[AuthClient] Failed to remove item from WeChat storage:",e);}},clear(){try{wx.removeStorageSync(l.ACCESS_TOKEN),wx.removeStorageSync(l.REFRESH_TOKEN),wx.removeStorageSync(l.USER);}catch(s){console.error("[AuthClient] Failed to clear WeChat storage:",s);}}}}function q(){let s=new Map;return {getItem(e){return s.get(e)??null},setItem(e,r){s.set(e,r);},removeItem(e){s.delete(e);},clear(){s.clear();}}}function B(s){try{let e=s.split(".");if(e.length!==3)return null;let t=(e[1]||"").replace(/-/g,"+").replace(/_/g,"/");if(typeof atob<"u"){let a=decodeURIComponent(atob(t).split("").map(o=>"%"+("00"+o.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(a)}if(typeof Buffer<"u"){let a=Buffer.from(t,"base64").toString("utf-8");return JSON.parse(a)}return null}catch(e){return console.error("[AuthClient] Failed to parse JWT token:",e),null}}var C=class{constructor(){this.refreshTimer=null;this.isRefreshing=false;this.refreshCallback=null;}setRefreshCallback(e){this.refreshCallback=e;}scheduleRefresh(e){this.clearSchedule(),!(e<=0)&&(this.refreshTimer=setTimeout(()=>{this.refresh();},e));}scheduleRefreshFromToken(e,r=300){let t=B(e);if(!t||!t.exp){console.warn("[AuthClient] Cannot schedule refresh: invalid token or missing exp claim");return}let a=t.exp*1e3,o=Date.now(),c=a-o-r*1e3;c<=0?(console.warn("[AuthClient] Token already expired or expiring soon, refreshing immediately"),this.refresh()):this.scheduleRefresh(c);}async refresh(){if(!this.isRefreshing){if(!this.refreshCallback){console.error("[AuthClient] No refresh callback set");return}this.isRefreshing=true;try{await this.refreshCallback();}catch(e){console.error("[AuthClient] Token refresh failed:",e);}finally{this.isRefreshing=false;}}}clearSchedule(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}isCurrentlyRefreshing(){return this.isRefreshing}destroy(){this.clearSchedule(),this.refreshCallback=null,this.isRefreshing=false;}};function v(s){return s&&typeof s=="object"&&("statusCode"in s||"status"in s)&&"data"in s?s.data:s}var R={transformResponse:v,logErrors:true};function k(s){let{http:e,onLoginSuccess:r,storage:t,clearAuth:a}=s;return {async register(o){let n=await e.request({url:"/api/auth/register",method:"post",headers:{"Content-Type":"application/json"},data:o});return n.data?.user&&n.data?.accessToken&&r(n.data.user,n.data.accessToken),n},async login(o){let n=await e.request({url:"/api/auth/login",method:"post",headers:{"Content-Type":"application/json"},data:o});return n.data?.user&&n.data?.accessToken&&r(n.data.user,n.data.accessToken),n},async loginWithCode(o){let n=await e.request({url:"/api/auth/login-with-code",method:"post",headers:{"Content-Type":"application/json"},data:o});return n.data?.user&&n.data?.accessToken&&r(n.data.user,n.data.accessToken),n},async sendCode(o){return e.request({url:"/api/auth/send-code",method:"post",headers:{"Content-Type":"application/json"},data:o})},async getCaptcha(){return e.request({url:"/api/auth/captcha",method:"get"})},loginWithOAuth(o,n){if(typeof window>"u"){console.error("[AuthClient] OAuth login is only available in browser environment");return}let c=n?`/api/auth/oauth/${o}?redirect_url=${encodeURIComponent(n)}`:`/api/auth/oauth/${o}`;window.location.href=c;},async handleOAuthCallback(){if(typeof window>"u")return {data:null,error:{message:"OAuth callback is only available in browser environment",status:400},status:400};try{let o=window.location.hash.substring(1),n=new URLSearchParams(o),c=n.get("access_token"),h=n.get("user");if(!c||!h)return {data:null,error:{message:"OAuth callback failed: missing token or user data",status:400},status:400};let u=JSON.parse(decodeURIComponent(h));return r(u,c),{data:{user:u,accessToken:c},error:null,status:200}}catch(o){return {data:null,error:{message:`OAuth callback failed: ${o instanceof Error?o.message:String(o)}`,status:400},status:400}}},async loginWithMiniProgram(o){let n=await e.request({url:"/api/auth/miniprogram/login",method:"post",headers:{"Content-Type":"application/json"},data:{code:o}});return n.data?.user&&n.data?.accessToken&&r(n.data.user,n.data.accessToken),n},async getMiniProgramPhoneNumber(o){let n=t.getItem("amaster_access_token");return n?e.request({url:"/api/auth/miniprogram/phone",method:"post",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},data:{code:o}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},async logout(){let o=t.getItem("amaster_access_token"),n=await e.request({url:"/api/auth/logout",method:"post",headers:o?{Authorization:`Bearer ${o}`}:void 0});return a(),n},async refreshToken(){return e.request({url:"/api/auth/refresh",method:"post"})}}}function P(s){let{getCurrentUser:e}=s;return {hasRole(r){let t=e();return !t||!t.roles?false:t.roles.includes(r)},hasPermission(r,t){let a=e();if(!a||!a.permissions)return false;let o=`${r}:${t}`;return a.permissions.includes(o)},hasAnyPermission(r){return r.some(({resource:t,action:a})=>this.hasPermission(t,a))},hasAllPermissions(r){return r.every(({resource:t,action:a})=>this.hasPermission(t,a))}}}function T(s){let{http:e,storage:r,onUserUpdate:t}=s;return {async getMe(){let a=r.getItem("amaster_access_token");if(!a)return {data:null,error:{message:"Not authenticated",status:401},status:401};let o=await e.request({url:"/api/auth/me",method:"get",headers:{Authorization:`Bearer ${a}`}});return o.data&&t(o.data),o},async updateMe(a){let o=r.getItem("amaster_access_token");if(!o)return {data:null,error:{message:"Not authenticated",status:401},status:401};let n=await e.request({url:"/api/auth/me",method:"put",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},data:a});return n.data&&t(n.data),n},async changePassword(a){let o=r.getItem("amaster_access_token");return o?e.request({url:"/api/auth/change-password",method:"post",headers:{Authorization:`Bearer ${o}`,"Content-Type":"application/json"},data:a}):{data:null,error:{message:"Not authenticated",status:401},status:401}}}}function w(s){let{http:e,storage:r}=s;return {async getOAuthBindings(){let t=r.getItem("amaster_access_token");return t?e.request({url:"/api/auth/oauth-bindings",method:"get",headers:{Authorization:`Bearer ${t}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},bindOAuth(t){if(typeof window>"u"){console.error("[AuthClient] OAuth binding is only available in browser environment");return}window.location.href=`/api/auth/oauth/${t}/bind`;},async unbindOAuth(t){let a=r.getItem("amaster_access_token");return a?e.request({url:`/api/auth/oauth/${t}/unbind`,method:"delete",headers:{Authorization:`Bearer ${a}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}}}}function E(s){let{http:e,storage:r}=s;return {async getSession(){let t=r.getItem("amaster_access_token");return t?e.request({url:"/api/auth/sessions/current",method:"get",headers:{Authorization:`Bearer ${t}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},async getSessions(){let t=r.getItem("amaster_access_token");return t?e.request({url:"/api/auth/sessions",method:"get",headers:{Authorization:`Bearer ${t}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},async revokeSession(t){let a=r.getItem("amaster_access_token");return a?t?e.request({url:`/api/auth/sessions/${t}`,method:"delete",headers:{Authorization:`Bearer ${a}`}}):{data:null,error:{message:"Session ID is required",status:400},status:400}:{data:null,error:{message:"Not authenticated",status:401},status:401}},async revokeAllSessions(){let t=r.getItem("amaster_access_token");return t?e.request({url:"/api/auth/sessions",method:"delete",headers:{Authorization:`Bearer ${t}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}}}}function z(s={},e){let {baseURL:r,headers:t,onTokenExpired:a,onUnauthorized:o}=s,n=e||httpClient.createHttpClient({...R,baseURL:r,headers:t}),h=300,u=S(),m=new y,f=new C,g=null;try{let i=u.getItem(l.USER);i&&(g=JSON.parse(i));}catch(i){console.error("[AuthClient] Failed to load user from storage:",i);}let d;f.setRefreshCallback(async()=>{let i=await d.refreshToken();i.data?(await d.getMe(),m.emit("tokenRefreshed",i.data.accessToken)):(m.emit("tokenExpired"),a?.());});function M(i,p){u.setItem(l.ACCESS_TOKEN,p),u.setItem(l.USER,JSON.stringify(i)),g=i,f.scheduleRefreshFromToken(p,h),m.emit("login",i);}function O(i){g=i,u.setItem(l.USER,JSON.stringify(i));}function A(){u.clear(),g=null,f.clearSchedule();}function U(){return g}let b=k({http:n,onLoginSuccess:M,storage:u,clearAuth:A}),x=P({getCurrentUser:U}),I=T({http:n,storage:u,onUserUpdate:O}),_=w({http:n,storage:u}),H=E({http:n,storage:u});return d={...b,...x,...I,..._,...H,on(i,p){m.on(i,p);},off(i,p){m.off(i,p);},isAuthenticated(){return !!u.getItem(l.ACCESS_TOKEN)},getAccessToken(){return u.getItem(l.ACCESS_TOKEN)},setAccessToken(i){u.setItem(l.ACCESS_TOKEN,i),f.scheduleRefreshFromToken(i,h);},clearAuth:A},d.on("unauthorized",()=>{o?.();}),d.isAuthenticated()&&d.getMe().catch(i=>{console.warn("[AuthClient] Failed to sync user info on init:",i);}),d}exports.createAuthClient=z;exports.defaultHttpClientOptions=R;exports.transformAmasterResponse=v;//# sourceMappingURL=index.cjs.map
1
+ 'use strict';var httpClient=require('@amaster.ai/http-client');var C=class{constructor(){this.events={};}on(e,n){this.events[e]||(this.events[e]=[]),this.events[e].push(n);}off(e,n){this.events[e]&&(this.events[e]=this.events[e].filter(r=>r!==n));}emit(e,...n){this.events[e]&&this.events[e].forEach(r=>{try{r(...n);}catch(a){console.error(`[AuthClient] Error in event handler for "${e}":`,a);}});}removeAllListeners(){this.events={};}};var d={ACCESS_TOKEN:"amaster_access_token",REFRESH_TOKEN:"amaster_refresh_token",USER:"amaster_user"};function N(){return typeof wx<"u"&&wx.getStorageSync?"wechat-miniprogram":typeof window<"u"&&typeof window.localStorage<"u"?"browser":"node"}function v(){switch(N()){case "wechat-miniprogram":return q();case "browser":return $();case "node":return B()}}function $(){let t=window.localStorage;return {getItem(e){try{return t.getItem(e)}catch(n){return console.error("[AuthClient] Failed to get item from localStorage:",n),null}},setItem(e,n){try{t.setItem(e,n);}catch(r){console.error("[AuthClient] Failed to set item in localStorage:",r);}},removeItem(e){try{t.removeItem(e);}catch(n){console.error("[AuthClient] Failed to remove item from localStorage:",n);}},clear(){try{t.removeItem(d.ACCESS_TOKEN),t.removeItem(d.REFRESH_TOKEN),t.removeItem(d.USER);}catch(e){console.error("[AuthClient] Failed to clear localStorage:",e);}}}}function q(){return {getItem(t){try{return wx.getStorageSync(t)||null}catch(e){return console.error("[AuthClient] Failed to get item from WeChat storage:",e),null}},setItem(t,e){try{wx.setStorageSync(t,e);}catch(n){console.error("[AuthClient] Failed to set item in WeChat storage:",n);}},removeItem(t){try{wx.removeStorageSync(t);}catch(e){console.error("[AuthClient] Failed to remove item from WeChat storage:",e);}},clear(){try{wx.removeStorageSync(d.ACCESS_TOKEN),wx.removeStorageSync(d.REFRESH_TOKEN),wx.removeStorageSync(d.USER);}catch(t){console.error("[AuthClient] Failed to clear WeChat storage:",t);}}}}function B(){let t=new Map;return {getItem(e){return t.get(e)??null},setItem(e,n){t.set(e,n);},removeItem(e){t.delete(e);},clear(){t.clear();}}}function F(t){try{let e=t.split(".");if(e.length!==3)return null;let r=(e[1]||"").replace(/-/g,"+").replace(/_/g,"/");if(typeof atob<"u"){let a=decodeURIComponent(atob(r).split("").map(s=>"%"+("00"+s.charCodeAt(0).toString(16)).slice(-2)).join(""));return JSON.parse(a)}if(typeof Buffer<"u"){let a=Buffer.from(r,"base64").toString("utf-8");return JSON.parse(a)}return null}catch(e){return console.error("[AuthClient] Failed to parse JWT token:",e),null}}var A=class{constructor(){this.refreshTimer=null;this.isRefreshing=false;this.refreshCallback=null;}setRefreshCallback(e){this.refreshCallback=e;}scheduleRefresh(e){this.clearSchedule(),!(e<=0)&&(this.refreshTimer=setTimeout(()=>{this.refresh();},e));}scheduleRefreshFromToken(e,n=300){let r=F(e);if(!r||!r.exp){console.warn("[AuthClient] Cannot schedule refresh: invalid token or missing exp claim");return}let a=r.exp*1e3,s=Date.now(),c=a-s-n*1e3;c<=0?(console.warn("[AuthClient] Token already expired or expiring soon, refreshing immediately"),this.refresh()):this.scheduleRefresh(c);}async refresh(){if(!this.isRefreshing){if(!this.refreshCallback){console.error("[AuthClient] No refresh callback set");return}this.isRefreshing=true;try{await this.refreshCallback();}catch(e){console.error("[AuthClient] Token refresh failed:",e);}finally{this.isRefreshing=false;}}}clearSchedule(){this.refreshTimer&&(clearTimeout(this.refreshTimer),this.refreshTimer=null);}isCurrentlyRefreshing(){return this.isRefreshing}destroy(){this.clearSchedule(),this.refreshCallback=null,this.isRefreshing=false;}};function k(t){return t&&typeof t=="object"&&("statusCode"in t||"status"in t)&&"data"in t?t.data:t}var R={transformResponse:k,logErrors:true};function z(t){if(t.email)return "email";if(t.username)return "username";if(t.phone)return "phone"}function K(t){if(t.email)return "email";if(t.phone)return "phone"}function j(t){let e=new URLSearchParams(t);return e.get("access_token")||e.get("accessToken")}function J(t){return new URLSearchParams(t).get("user")}function D(){if(typeof window>"u")return null;try{let t=new URLSearchParams(window.location.search).get("redirect");if(!t)return null;let e=new URL(t,window.location.origin);return e.origin!==window.location.origin?(console.warn("[AuthClient] Ignored cross-origin redirect target:",t),null):`${e.pathname}${e.search}${e.hash}`}catch(t){return console.warn("[AuthClient] Failed to resolve redirect target:",t),null}}var W={replace(t){window.location.replace(t);}};function w(t){let{http:e,onLoginSuccess:n,storage:r,clearAuth:a}=t;return {async register(s){let o=await e.request({url:"/api/auth/register",method:"post",headers:{"Content-Type":"application/json"},data:s});return o.data?.user&&o.data?.accessToken&&n(o.data.user,o.data.accessToken),o},async login(s){let o=s.loginType||z(s);if(!o)return {data:null,error:{message:"Unable to determine login type. Please provide email, username, or phone.",status:400},status:400};let c={...s,loginType:o},l=await e.request({url:"/api/auth/login",method:"post",headers:{"Content-Type":"application/json"},data:c});return l.data?.user&&l.data?.accessToken&&n(l.data.user,l.data.accessToken),l},async loginWithCode(s){let o=s.loginType||K(s);if(!o)return {data:null,error:{message:"Unable to determine login type. Please provide email or phone.",status:400},status:400};let c={...s,loginType:o},l=await e.request({url:"/api/auth/login-with-code",method:"post",headers:{"Content-Type":"application/json"},data:c});return l.data?.user&&l.data?.accessToken&&n(l.data.user,l.data.accessToken),l},async sendCode(s){return e.request({url:"/api/auth/send-code",method:"post",headers:{"Content-Type":"application/json"},data:s})},async getCaptcha(){return e.request({url:"/api/auth/captcha",method:"get"})},loginWithOAuth(s,o){if(typeof window>"u"){console.error("[AuthClient] OAuth login is only available in browser environment");return}let c=o?`/api/auth/oauth/${s}?redirect_url=${encodeURIComponent(o)}`:`/api/auth/oauth/${s}`;window.location.href=c;},async handleOAuthCallback(){if(typeof window>"u")return {data:null,error:{message:"OAuth callback is only available in browser environment",status:400},status:400};try{let s=window.location.hash.substring(1),o=j(s),c=J(s);if(!o)return {data:null,error:{message:"OAuth callback failed: missing access token",status:400},status:400};let l;if(c)l=JSON.parse(decodeURIComponent(c));else {r.setItem(d.ACCESS_TOKEN,o);let u=await e.request({url:"/api/auth/me",method:"get",headers:{Authorization:`Bearer ${o}`}});if(!u.data)return {data:null,error:{message:"OAuth callback failed: unable to fetch user info",status:u.status||500},status:u.status||500};l=u.data;}if(n(l,o),window.history&&window.history.replaceState){let u=window.location.pathname+window.location.search;window.history.replaceState(null,"",u);}else window.location.hash="";let m=D();return m&&!window.opener&&W.replace(m),{data:{user:l,accessToken:o},error:null,status:200}}catch(s){return {data:null,error:{message:`OAuth callback failed: ${s instanceof Error?s.message:String(s)}`,status:400},status:400}}},async loginWithMiniProgram(s){let o=await e.request({url:"/api/auth/miniprogram/login",method:"post",headers:{"Content-Type":"application/json"},data:{code:s}});return o.data?.user&&o.data?.accessToken&&n(o.data.user,o.data.accessToken),o},async getMiniProgramPhoneNumber(s){let o=r.getItem("amaster_access_token");return o?e.request({url:"/api/auth/miniprogram/phone",method:"post",headers:{"Content-Type":"application/json",Authorization:`Bearer ${o}`},data:{code:s}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},async logout(){let s=r.getItem("amaster_access_token"),o=await e.request({url:"/api/auth/logout",method:"post",headers:s?{Authorization:`Bearer ${s}`}:void 0});return a(),o},async refreshToken(){let s=await e.request({url:"/api/auth/refresh",method:"post"});return s.data?.accessToken&&r.setItem("amaster_access_token",s.data.accessToken),s}}}function T(t){let{getCurrentUser:e}=t;return {hasRole(n){let r=e();return !r||!r.roles?false:r.roles.includes(n)},hasPermission(n,r){let a=e();if(!a||!a.permissions)return false;let s=`${n}.${r}`;return a.permissions.includes(s)},hasAnyPermission(n){return n.some(({resource:r,action:a})=>this.hasPermission(r,a))},hasAllPermissions(n){return n.every(({resource:r,action:a})=>this.hasPermission(r,a))}}}function P(t){let{http:e,storage:n,onUserUpdate:r}=t;return {async getMe(){let a=n.getItem("amaster_access_token");if(!a)return {data:null,error:{message:"Not authenticated",status:401},status:401};let s=await e.request({url:"/api/auth/me",method:"get",headers:{Authorization:`Bearer ${a}`}});return s.data&&r(s.data),s},async updateMe(a){let s=n.getItem("amaster_access_token");if(!s)return {data:null,error:{message:"Not authenticated",status:401},status:401};let o=await e.request({url:"/api/auth/me",method:"put",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},data:a});return o.data&&r(o.data),o},async changePassword(a){let s=n.getItem("amaster_access_token");return s?e.request({url:"/api/auth/change-password",method:"post",headers:{Authorization:`Bearer ${s}`,"Content-Type":"application/json"},data:a}):{data:null,error:{message:"Not authenticated",status:401},status:401}}}}function E(t){let{http:e,storage:n}=t;return {async getOAuthBindings(){let r=n.getItem("amaster_access_token");return r?e.request({url:"/api/auth/oauth-bindings",method:"get",headers:{Authorization:`Bearer ${r}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},bindOAuth(r){if(typeof window>"u"){console.error("[AuthClient] OAuth binding is only available in browser environment");return}window.location.href=`/api/auth/oauth/${r}/bind`;},async unbindOAuth(r){let a=n.getItem("amaster_access_token");return a?e.request({url:`/api/auth/oauth/${r}/unbind`,method:"delete",headers:{Authorization:`Bearer ${a}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}}}}function O(t){let{http:e,storage:n}=t;return {async getSession(){let r=n.getItem("amaster_access_token");return r?e.request({url:"/api/auth/sessions/current",method:"get",headers:{Authorization:`Bearer ${r}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},async getSessions(){let r=n.getItem("amaster_access_token");return r?e.request({url:"/api/auth/sessions",method:"get",headers:{Authorization:`Bearer ${r}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}},async revokeSession(r){let a=n.getItem("amaster_access_token");return a?r?e.request({url:`/api/auth/sessions/${r}`,method:"delete",headers:{Authorization:`Bearer ${a}`}}):{data:null,error:{message:"Session ID is required",status:400},status:400}:{data:null,error:{message:"Not authenticated",status:401},status:401}},async revokeAllSessions(){let r=n.getItem("amaster_access_token");return r?e.request({url:"/api/auth/sessions",method:"delete",headers:{Authorization:`Bearer ${r}`}}):{data:null,error:{message:"Not authenticated",status:401},status:401}}}}function Y(t={},e){let {baseURL:n,headers:r,onTokenExpired:a,onUnauthorized:s,autoHandleOAuthCallback:o=true}=t,c=e||httpClient.createHttpClient({...R,baseURL:n,headers:r}),m=300,u=v(),g=new C,y=new A,f=null;try{let i=u.getItem(d.USER);i&&(f=JSON.parse(i));}catch(i){console.error("[AuthClient] Failed to load user from storage:",i);}let p;y.setRefreshCallback(async()=>{let i=await p.refreshToken();i.data?(await p.getMe(),g.emit("tokenRefreshed",i.data.accessToken)):(g.emit("tokenExpired"),a?.());});function M(i,h){u.setItem(d.ACCESS_TOKEN,h),u.setItem(d.USER,JSON.stringify(i)),f=i,y.scheduleRefreshFromToken(h,m),g.emit("login",i);}function U(i){f=i,u.setItem(d.USER,JSON.stringify(i));}function S(){u.clear(),f=null,y.clearSchedule();}function b(){return f}let I=w({http:c,onLoginSuccess:M,storage:u,clearAuth:S}),x=T({getCurrentUser:b}),_=P({http:c,storage:u,onUserUpdate:U}),L=E({http:c,storage:u}),H=O({http:c,storage:u});if(p={...I,...x,..._,...L,...H,on(i,h){g.on(i,h);},off(i,h){g.off(i,h);},isAuthenticated(){return !!u.getItem(d.ACCESS_TOKEN)},getAccessToken(){return u.getItem(d.ACCESS_TOKEN)},setAccessToken(i){u.setItem(d.ACCESS_TOKEN,i),y.scheduleRefreshFromToken(i,m);},clearAuth:S},p.on("unauthorized",()=>{s?.();}),p.isAuthenticated()&&p.getMe().catch(i=>{console.warn("[AuthClient] Failed to sync user info on init:",i);}),o&&typeof window<"u"){let i=window.location.hash;i&&(i.includes("access_token")||i.includes("accessToken"))&&p.handleOAuthCallback().then(h=>{h.error&&console.error("[AuthClient] Auto OAuth callback failed:",h.error);}).catch(h=>{console.error("[AuthClient] Auto OAuth callback error:",h);});}return p}exports.createAuthClient=Y;exports.defaultHttpClientOptions=R;exports.transformAmasterResponse=k;//# sourceMappingURL=index.cjs.map
2
2
  //# sourceMappingURL=index.cjs.map