@cloudbase/auth 3.1.8 → 3.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/auth",
3
- "version": "3.1.8",
3
+ "version": "3.1.9",
4
4
  "description": "cloudbase javascript sdk auth componets",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -32,14 +32,14 @@
32
32
  "license": "Apache-2.0",
33
33
  "dependencies": {
34
34
  "@cloudbase/adapter-wx_mp": "^1.3.1",
35
- "@cloudbase/oauth": "3.1.8",
36
- "@cloudbase/utilities": "3.1.8"
35
+ "@cloudbase/oauth": "3.1.9",
36
+ "@cloudbase/utilities": "3.1.9"
37
37
  },
38
38
  "devDependencies": {
39
- "@cloudbase/types": "3.1.8",
39
+ "@cloudbase/types": "3.1.9",
40
40
  "@types/node": "^22.1.0",
41
41
  "terser-webpack-plugin": "^3.0.2",
42
42
  "webpack-bundle-analyzer": "^4.9.1"
43
43
  },
44
- "gitHead": "9b3a35f964e58895e3675616428be99a85850fa7"
44
+ "gitHead": "d354f0b4080b9cf208a499966328cc33954b3c19"
45
45
  }
package/src/index.ts CHANGED
@@ -29,6 +29,13 @@ import {
29
29
  useDefaultAdapter,
30
30
  } from './utilities'
31
31
  import { saveToBrowserSession, getBrowserSession, removeBrowserSession, addUrlSearch } from './utils'
32
+ import {
33
+ AuthV1Compat,
34
+ WeixinAuthProvider,
35
+ CustomAuthProvider,
36
+ AnonymousAuthProvider,
37
+ applyUserV1Compat,
38
+ } from './v1-compat'
32
39
  import { utils } from '@cloudbase/utilities'
33
40
  import {
34
41
  CommonRes,
@@ -329,6 +336,10 @@ export class User implements IUser {
329
336
  })
330
337
  }
331
338
  }
339
+
340
+ // 应用 v1 兼容方法到 User 类
341
+ applyUserV1Compat(User)
342
+
332
343
  interface ILoginStateOptions extends IUserOptions {
333
344
  envId: string
334
345
  }
@@ -370,7 +381,7 @@ interface IAuthConfig extends ICloudbaseAuthConfig {
370
381
  runtime?: string
371
382
  }
372
383
 
373
- class Auth {
384
+ class Auth extends AuthV1Compat {
374
385
  readonly config: IAuthConfig
375
386
  oauthInstance: CloudbaseOAuth
376
387
  readonly cache: ICloudbaseCache
@@ -378,6 +389,7 @@ class Auth {
378
389
  private hasListenerSetUp = false
379
390
 
380
391
  constructor(config: IAuthConfig) {
392
+ super()
381
393
  this.config = config
382
394
  this.oauthInstance = config.oauthInstance
383
395
  this.cache = config.cache
@@ -1124,6 +1136,8 @@ class Auth {
1124
1136
  callback.call(this, loginState)
1125
1137
  }
1126
1138
 
1139
+ // v1 兼容 API 方法已移至 AuthV1Compat 基类 (v1-compat.ts)
1140
+
1127
1141
  /**
1128
1142
  * 强制刷新token
1129
1143
  * @param params
@@ -1218,7 +1232,7 @@ class Auth {
1218
1232
  const { verification_token } = verifyRes
1219
1233
 
1220
1234
  // 手机登录参数
1221
- let username = /^\+\d{1,3}\s+/.test(rawUsername) ? rawUsername : `+86 ${rawUsername}`
1235
+ let username = this.formatPhone(rawUsername)
1222
1236
  let signUpParam: any = { phone_number: username }
1223
1237
 
1224
1238
  // 邮箱登录参数
@@ -1504,7 +1518,16 @@ class Auth {
1504
1518
 
1505
1519
  return { data: { user: session.user, session }, error: null }
1506
1520
  } catch (error) {
1507
- return { data: {}, error: new AuthError(error) }
1521
+ const authError = new AuthError(error)
1522
+ // 优化错误提示:登录失败时提供更友好的排查指引
1523
+ if (authError.message?.includes('密码不正确') || authError.message?.includes('password')) {
1524
+ console.warn('[CloudBase Auth] 登录失败提示:\n'
1525
+ + ' 1. 请确认用户名/邮箱/手机号是否正确\n'
1526
+ + ' 2. 请确认密码是否正确\n'
1527
+ + ' 3. 如果用户不存在,请先通过 auth.signUp() 注册用户,或在云开发控制台手动创建用户\n'
1528
+ + ' 4. 确认当前环境已开启对应的登录方式(控制台 → 环境 → 登录授权)',)
1529
+ }
1530
+ return { data: {}, error: authError }
1508
1531
  }
1509
1532
  }
1510
1533
 
@@ -2457,7 +2480,7 @@ class Auth {
2457
2480
  return { ...(loginState as any), data: { user: session.user, session }, error: null }
2458
2481
  }
2459
2482
 
2460
- private formatPhone(phone: string) {
2483
+ protected formatPhone(phone: string) {
2461
2484
  if (!/\s+/.test(phone) && /^\+\d{1,3}\d+/.test(phone)) {
2462
2485
  return phone.replace(/^(\+\d{1,2})(\d+)$/, '$1 $2')
2463
2486
  }
@@ -2687,6 +2710,52 @@ export function generateAuthInstance(
2687
2710
 
2688
2711
  const NAMESPACE = 'auth'
2689
2712
 
2713
+ /**
2714
+ * 计算两个字符串之间的编辑距离(Levenshtein distance)
2715
+ * 用于在用户调用不存在的 Auth 方法时,推荐最相似的方法名
2716
+ */
2717
+ function levenshteinDistance(a: string, b: string): number {
2718
+ const matrix: number[][] = []
2719
+ for (let i = 0; i <= a.length; i++) {
2720
+ matrix[i] = [i]
2721
+ }
2722
+ for (let j = 0; j <= b.length; j++) {
2723
+ matrix[0][j] = j
2724
+ }
2725
+ for (let i = 1; i <= a.length; i++) {
2726
+ for (let j = 1; j <= b.length; j++) {
2727
+ const cost = a[i - 1].toLowerCase() === b[j - 1].toLowerCase() ? 0 : 1
2728
+ matrix[i][j] = Math.min(
2729
+ matrix[i - 1][j] + 1,
2730
+ matrix[i][j - 1] + 1,
2731
+ matrix[i - 1][j - 1] + cost,
2732
+ )
2733
+ }
2734
+ }
2735
+ return matrix[a.length][b.length]
2736
+ }
2737
+
2738
+ /**
2739
+ * 在方法列表中查找与目标名称最相似的方法
2740
+ * @returns 最相似的方法名,如果没有足够相似的则返回 null
2741
+ */
2742
+ function findSimilarMethod(target: string, methods: string[]): string | null {
2743
+ let bestMatch: string | null = null
2744
+ let bestDistance = Infinity
2745
+ const maxDistance = Math.max(3, Math.floor(target.length * 0.4)) // 允许 40% 的编辑距离
2746
+
2747
+ for (const method of methods) {
2748
+ // 跳过私有方法和构造函数
2749
+ if (method.startsWith('_') || method === 'constructor') continue
2750
+ const distance = levenshteinDistance(target, method)
2751
+ if (distance < bestDistance && distance <= maxDistance) {
2752
+ bestDistance = distance
2753
+ bestMatch = method
2754
+ }
2755
+ }
2756
+ return bestMatch
2757
+ }
2758
+
2690
2759
  const component: ICloudbaseComponent = {
2691
2760
  name: COMPONENT_NAME,
2692
2761
  namespace: NAMESPACE,
@@ -2742,8 +2811,30 @@ const component: ICloudbaseComponent = {
2742
2811
  const authProto = auth.call(this, config)
2743
2812
  Object.assign(auth, authProto)
2744
2813
  Object.setPrototypeOf(auth, Object.getPrototypeOf(authProto))
2745
- this[NAMESPACE] = auth
2746
- return auth
2814
+
2815
+ // 包装 Proxy,当用户调用不存在的方法时提供 "Did you mean?" 提示
2816
+ const authWithHint = typeof Proxy !== 'undefined'
2817
+ ? new Proxy(auth, {
2818
+ get(target, prop, receiver) {
2819
+ const value = Reflect.get(target, prop, receiver)
2820
+ if (value !== undefined || typeof prop !== 'string') {
2821
+ return value
2822
+ }
2823
+ // 查找最相似的方法名
2824
+ const allKeys = Object.getOwnPropertyNames(Object.getPrototypeOf(target))
2825
+ .concat(Object.keys(target))
2826
+ .filter(k => typeof target[k] === 'function')
2827
+ const suggestion = findSimilarMethod(prop, allKeys)
2828
+ if (suggestion) {
2829
+ console.warn(`[CloudBase Auth] auth.${prop} is not a function. Did you mean: auth.${suggestion}() ?`)
2830
+ }
2831
+ return value
2832
+ },
2833
+ })
2834
+ : auth
2835
+
2836
+ this[NAMESPACE] = authWithHint
2837
+ return authWithHint
2747
2838
  },
2748
2839
  }
2749
2840
 
@@ -2753,7 +2844,7 @@ try {
2753
2844
  cloudbase.registerComponent(component)
2754
2845
  } catch (e) {}
2755
2846
 
2756
- export { UserInfo, Auth }
2847
+ export { UserInfo, Auth, WeixinAuthProvider, CustomAuthProvider, AnonymousAuthProvider }
2757
2848
  /**
2758
2849
  * @api 手动注册至cloudbase app
2759
2850
  */
@@ -0,0 +1,499 @@
1
+ /* eslint-disable @typescript-eslint/no-useless-constructor */
2
+ /**
3
+ * v1 兼容层
4
+ * 本文件包含 v1 版本的 Provider 类、Auth v1 兼容方法和 User v1 兼容方法。
5
+ * Auth 类通过 extends AuthV1Compat 来继承 v1 方法。
6
+ * User 类通过 applyUserV1Compat mixin 来扩展 v1 方法。
7
+ */
8
+ import type { authModels } from '@cloudbase/oauth'
9
+ import {
10
+ LOGIN_STATE_CHANGED_TYPE,
11
+ AUTH_STATE_CHANGED_TYPE,
12
+ } from '@cloudbase/oauth'
13
+ import {
14
+ throwError,
15
+ ERRORS,
16
+ } from './utilities'
17
+ import type { SignUpRes } from './type'
18
+
19
+ // ========== v1 兼容 Provider 类 ==========
20
+
21
+ /**
22
+ * v1 微信登录 Provider
23
+ * 使用方式: auth.weixinAuthProvider({ appid, scope }).signInWithRedirect()
24
+ *
25
+ * @deprecated v1 兼容 API。建议使用 auth.signInWithOAuth({ provider }) 替代。
26
+ */
27
+ export class WeixinAuthProvider {
28
+ constructor(_options: {
29
+ authInstance: any
30
+ appid: string
31
+ scope: string
32
+ }) {
33
+ // v1 兼容类,所有方法均抛出异常提示替代方案
34
+ }
35
+
36
+ /**
37
+ * @deprecated v1 兼容 API。
38
+ * 建议使用 auth.signInWithOAuth({ provider: 'providerId' }) 替代。
39
+ */
40
+ public signInWithRedirect(): void {
41
+ throw new Error('[v1 兼容] WeixinAuthProvider.signInWithRedirect() 在当前版本中无法实现。'
42
+ + ' 建议使用 auth.signInWithOAuth({ provider: \'providerId\' }) 替代。',)
43
+ }
44
+
45
+ /**
46
+ * @deprecated v1 兼容 API。
47
+ * 建议使用 auth.verifyOAuth({ code, state, provider }) 替代。
48
+ */
49
+ public async getRedirectResult(_options?: {
50
+ createUser?: boolean
51
+ syncUserInfo?: boolean
52
+ }): Promise<any> {
53
+ throw new Error('[v1 兼容] WeixinAuthProvider.getRedirectResult() 在当前版本中无法实现。'
54
+ + ' 建议使用 auth.verifyOAuth({ code, state, provider }) 替代。',)
55
+ }
56
+
57
+ /**
58
+ * @deprecated v1 兼容 API。
59
+ * 建议使用 auth.linkIdentity({ provider: 'providerId' }) 替代。
60
+ */
61
+ public async getLinkRedirectResult(_options?: {
62
+ withUnionId?: boolean
63
+ }): Promise<void> {
64
+ throw new Error('[v1 兼容] WeixinAuthProvider.getLinkRedirectResult() 在当前版本中无法实现。'
65
+ + ' 建议使用 auth.linkIdentity({ provider: \'providerId\' }) 替代。',)
66
+ }
67
+ }
68
+
69
+ /**
70
+ * v1 自定义登录 Provider
71
+ * 使用方式: auth.customAuthProvider().signIn(ticket)
72
+ *
73
+ * @deprecated v1 兼容 API。建议使用 auth.signInWithCustomTicket(() => Promise.resolve(ticket)) 替代。
74
+ */
75
+ export class CustomAuthProvider {
76
+ private authInstance: any
77
+
78
+ constructor(options: {
79
+ authInstance: any
80
+ }) {
81
+ this.authInstance = options.authInstance
82
+ }
83
+
84
+ /**
85
+ * 使用自定义登录凭据 ticket 登录云开发
86
+ * @param ticket 自定义登录 ticket
87
+ * @deprecated v1 兼容 API。建议使用 auth.signInWithCustomTicket(() => Promise.resolve(ticket)) 替代。
88
+ */
89
+ public async signIn(ticket: string): Promise<void> {
90
+ // 使用 Auth 已有的 signInWithCustomTicket 方法
91
+ await this.authInstance.signInWithCustomTicket(() => Promise.resolve(ticket))
92
+ }
93
+ }
94
+
95
+ /**
96
+ * v1 匿名登录 Provider
97
+ * 使用方式: auth.anonymousAuthProvider().signIn()
98
+ *
99
+ * @deprecated v1 兼容 API。建议使用 auth.signInAnonymously({}) 替代。
100
+ */
101
+ export class AnonymousAuthProvider {
102
+ private authInstance: any
103
+
104
+ constructor(options: {
105
+ authInstance: any
106
+ }) {
107
+ this.authInstance = options.authInstance
108
+ }
109
+
110
+ /**
111
+ * 匿名登录云开发
112
+ * @deprecated v1 兼容 API。建议使用 auth.signInAnonymously({}) 替代。
113
+ */
114
+ public async signIn(): Promise<void> {
115
+ // 使用 Auth 已有的 signInAnonymously 方法
116
+ await this.authInstance.signInAnonymously({})
117
+ }
118
+ }
119
+
120
+ // ========== Auth v1 兼容基类 ==========
121
+
122
+ /**
123
+ * AuthV1Compat 基类,提供 v1 版本的兼容 API 方法。
124
+ * Auth 类通过 extends AuthV1Compat 来继承这些方法。
125
+ *
126
+ * 注意:此类中的方法通过 this 访问子类 Auth 的方法和属性,
127
+ * 包括: signIn, signUp, signInWithCustomTicket, signInAnonymously,
128
+ * getVerification, resetPassword, onLoginStateChanged, onAuthStateChange,
129
+ * createLoginState, formatPhone, config, signInWithOAuth 等。
130
+ */
131
+ // eslint-disable @typescript-eslint/member-ordering -- abstract 基类中 abstract 声明与 public 方法排序不适用常规规则
132
+ export abstract class AuthV1Compat {
133
+ abstract readonly config: any
134
+
135
+ // ========== v1 兼容 API 方法 ==========
136
+
137
+ /**
138
+ * v1 API: 获取用于微信登录的 WeixinAuthProvider // cspell:ignore weixin Weixin
139
+ * @deprecated 建议使用 auth.signInWithOAuth({ provider: appid }) 替代。
140
+ */
141
+ public weixinAuthProvider(options: { appid: string; scope: string }): WeixinAuthProvider {
142
+ return new WeixinAuthProvider({
143
+ authInstance: this,
144
+ appid: options.appid,
145
+ scope: options.scope,
146
+ })
147
+ }
148
+
149
+ /**
150
+ * v1 API: 获取用于自定义登录的 CustomAuthProvider
151
+ * @deprecated 建议使用 auth.signInWithCustomTicket(() => Promise.resolve(ticket)) 替代。
152
+ */
153
+ public customAuthProvider(): CustomAuthProvider {
154
+ return new CustomAuthProvider({
155
+ authInstance: this,
156
+ })
157
+ }
158
+
159
+ /**
160
+ * v1 API: 获取用于匿名登录的 AnonymousAuthProvider
161
+ * @deprecated 建议使用 auth.signInAnonymously({}) 替代。
162
+ */
163
+ public anonymousAuthProvider(): AnonymousAuthProvider {
164
+ return new AnonymousAuthProvider({
165
+ authInstance: this,
166
+ })
167
+ }
168
+
169
+ /**
170
+ * v1 API: 使用邮箱和密码注册云开发账户
171
+ * @deprecated 建议使用 auth.signUp({ email, password }) 替代。
172
+ */
173
+ public async signUpWithEmailAndPassword(email: string, password: string): Promise<SignUpRes> {
174
+ if (typeof email !== 'string') {
175
+ throwError(ERRORS.INVALID_PARAMS, 'email must be a string')
176
+ }
177
+ if (typeof password !== 'string') {
178
+ throwError(ERRORS.INVALID_PARAMS, 'password must be a string')
179
+ }
180
+
181
+ // 使用 Auth 已有的 signUp 方法
182
+ return await this.signUp({
183
+ email,
184
+ password,
185
+ })
186
+ }
187
+
188
+ /**
189
+ * v1 API: 使用邮箱和密码登录云开发
190
+ * @deprecated 建议使用 auth.signInWithPassword({ email, password }) 替代。
191
+ */
192
+ public async signInWithEmailAndPassword(email: string, password: string): Promise<any> {
193
+ if (typeof email !== 'string') {
194
+ throwError(ERRORS.INVALID_PARAMS, 'email must be a string')
195
+ }
196
+ if (typeof password !== 'string') {
197
+ throwError(ERRORS.INVALID_PARAMS, 'password must be a string')
198
+ }
199
+
200
+ // 使用 Auth 已有的 signIn 方法
201
+ await this.signIn({
202
+ username: email,
203
+ password,
204
+ })
205
+ return this.createLoginState()
206
+ }
207
+
208
+ /**
209
+ * v1 API: 发送重置密码的邮件
210
+ * @deprecated 建议使用 auth.resetPasswordForEmail(email) 替代。
211
+ */
212
+ public async sendPasswordResetEmail(email: string): Promise<void> {
213
+ if (typeof email !== 'string') {
214
+ throwError(ERRORS.INVALID_PARAMS, 'email must be a string')
215
+ }
216
+
217
+ // 使用 Auth 已有的 getVerification 方法
218
+ await this.getVerification({ email })
219
+ }
220
+
221
+ /**
222
+ * v1 API: 使用用户名密码登录云开发
223
+ * @deprecated 建议使用 auth.signInWithPassword({ username, password }) 替代。
224
+ */
225
+ public async signInWithUsernameAndPassword(username: string, password: string): Promise<any> {
226
+ if (typeof username !== 'string') {
227
+ throwError(ERRORS.INVALID_PARAMS, 'username must be a string')
228
+ }
229
+ if (typeof password !== 'string') {
230
+ throwError(ERRORS.INVALID_PARAMS, 'password must be a string')
231
+ }
232
+
233
+ // 使用 Auth 已有的 signIn 方法
234
+ await this.signIn({
235
+ username,
236
+ password,
237
+ })
238
+ return this.createLoginState()
239
+ }
240
+
241
+ /**
242
+ * v1 API: 发送手机验证码
243
+ * @deprecated 建议使用 auth.signInWithOtp({ phone }) 或 auth.getVerification({ phone_number }) 替代。
244
+ */
245
+ public async sendPhoneCode(phoneNumber: string): Promise<boolean> {
246
+ if (typeof phoneNumber !== 'string') {
247
+ throwError(ERRORS.INVALID_PARAMS, 'phoneNumber must be a string')
248
+ }
249
+
250
+ // 使用 Auth 已有的 getVerification 方法
251
+ const formattedPhone = this.formatPhone(phoneNumber)
252
+ await this.getVerification({ phone_number: formattedPhone })
253
+ return true
254
+ }
255
+
256
+ /**
257
+ * v1 API: 手机号注册(支持短信验证码+密码方式)
258
+ * @deprecated 建议使用 auth.signUp({ phone_number, verification_code, password? }) 替代。
259
+ */
260
+ public async signUpWithPhoneCode(phoneNumber: string, phoneCode: string, password?: string): Promise<any> {
261
+ if (typeof phoneNumber !== 'string') {
262
+ throwError(ERRORS.INVALID_PARAMS, 'phoneNumber must be a string')
263
+ }
264
+ if (typeof phoneCode !== 'string') {
265
+ throwError(ERRORS.INVALID_PARAMS, 'phoneCode must be a string')
266
+ }
267
+
268
+ const formattedPhone = this.formatPhone(phoneNumber)
269
+
270
+ // 使用 Auth 已有的 signUp 方法
271
+ await this.signUp({
272
+ phone_number: formattedPhone,
273
+ verification_code: phoneCode,
274
+ ...(password ? { password } : {}),
275
+ })
276
+ return this.createLoginState()
277
+ }
278
+
279
+ /**
280
+ * v1 API: 手机号登录(支持短信验证码 or 密码方式)
281
+ * @deprecated 密码方式建议使用 auth.signInWithPassword({ phone, password }) 替代;
282
+ * 验证码方式建议使用 auth.signInWithOtp({ phone }) 替代。
283
+ */
284
+ public async signInWithPhoneCodeOrPassword(params: {
285
+ phoneNumber: string
286
+ phoneCode?: string
287
+ password?: string
288
+ }): Promise<any> {
289
+ const { phoneNumber, phoneCode, password } = params
290
+ if (typeof phoneNumber !== 'string') {
291
+ throwError(ERRORS.INVALID_PARAMS, 'phoneNumber must be a string')
292
+ }
293
+
294
+ const formattedPhone = this.formatPhone(phoneNumber)
295
+
296
+ if (password) {
297
+ // 使用 Auth 已有的 signIn 方法进行密码登录
298
+ await this.signIn({
299
+ username: formattedPhone,
300
+ password,
301
+ })
302
+ } else if (phoneCode) {
303
+ // 使用 Auth 已有的 signIn 方法进行验证码登录
304
+ await this.signIn({
305
+ username: formattedPhone,
306
+ verification_token: phoneCode,
307
+ } as any)
308
+ } else {
309
+ throwError(ERRORS.INVALID_PARAMS, 'phoneCode or password must be provided')
310
+ }
311
+ return this.createLoginState()
312
+ }
313
+
314
+ /**
315
+ * v1 API: 手机号强制重置密码
316
+ * @deprecated 建议使用 auth.resetPasswordForEmail(phoneNumber) 替代。
317
+ */
318
+ public async forceResetPwdByPhoneCode(params: {
319
+ phoneNumber: string
320
+ phoneCode: string
321
+ password: string
322
+ }): Promise<any> {
323
+ const { phoneNumber, phoneCode, password } = params
324
+ if (typeof phoneNumber !== 'string') {
325
+ throwError(ERRORS.INVALID_PARAMS, 'phoneNumber must be a string')
326
+ }
327
+ if (typeof phoneCode !== 'string') {
328
+ throwError(ERRORS.INVALID_PARAMS, 'phoneCode must be a string')
329
+ }
330
+ if (typeof password !== 'string') {
331
+ throwError(ERRORS.INVALID_PARAMS, 'password must be a string')
332
+ }
333
+
334
+ const formattedPhone = this.formatPhone(phoneNumber)
335
+
336
+ // 使用 Auth 已有的 resetPassword 方法
337
+ await this.resetPassword({
338
+ phone_number: formattedPhone,
339
+ new_password: password,
340
+ verification_token: phoneCode,
341
+ })
342
+
343
+ // 重置密码成功后使用 Auth 已有的 signIn 方法自动登录
344
+ await this.signIn({
345
+ username: formattedPhone,
346
+ password,
347
+ })
348
+ return this.createLoginState()
349
+ }
350
+
351
+ /**
352
+ * v1 API: 接收一个回调函数,在刷新短期访问令牌前调用,根据返回值决定是否刷新
353
+ * @deprecated v1 兼容 API,当前 v3 SDK 中无直接对应方法。
354
+ * 建议使用 auth.onAuthStateChange(callback) 监听 TOKEN_REFRESHED 事件替代。
355
+ */
356
+ public shouldRefreshAccessToken(_callback: () => boolean): void {
357
+ throw new Error('[v1 兼容] shouldRefreshAccessToken() 在当前版本中无法实现。'
358
+ + ' 建议使用 auth.onAuthStateChange(callback) 来监听 TOKEN_REFRESHED 事件替代。',)
359
+ }
360
+
361
+ /**
362
+ * v1 API: 接收一个回调函数,在登录状态过期时调用
363
+ * @deprecated 建议使用 auth.onAuthStateChange(callback) 替代,监听 SIGNED_OUT 事件。
364
+ */
365
+ public onLoginStateExpired(callback: Function): void {
366
+ // 使用 Auth 已有的 onLoginStateChanged 方法
367
+ this.onLoginStateChanged((params) => {
368
+ if (params?.eventType === LOGIN_STATE_CHANGED_TYPE.CREDENTIALS_ERROR) {
369
+ callback.call(this)
370
+ }
371
+ })
372
+ }
373
+
374
+ /**
375
+ * v1 API: 接收一个回调函数,在短期访问令牌刷新后调用
376
+ * @deprecated 建议使用 auth.onAuthStateChange(callback) 替代,监听 TOKEN_REFRESHED 事件。
377
+ */
378
+ public onAccessTokenRefreshed(callback: Function): void {
379
+ // 使用 Auth 已有的 onAuthStateChange 方法
380
+ this.onAuthStateChange((event) => {
381
+ if (event === AUTH_STATE_CHANGED_TYPE.TOKEN_REFRESHED) {
382
+ callback.call(this)
383
+ }
384
+ })
385
+ }
386
+
387
+ /**
388
+ * v1 API: 接收一个回调函数,在匿名登录状态被转换后调用
389
+ * @deprecated 建议使用 auth.onAuthStateChange(callback) 替代,监听 SIGNED_IN 事件。
390
+ */
391
+ public onAnonymousConverted(callback: Function): void {
392
+ // 使用 Auth 已有的 onAuthStateChange 方法
393
+ this.onAuthStateChange((event) => {
394
+ if (event === AUTH_STATE_CHANGED_TYPE.SIGNED_IN) {
395
+ callback.call(this)
396
+ }
397
+ })
398
+ }
399
+
400
+ /**
401
+ * v1 API: 接收一个回调函数,在登录类型发生变化后调用
402
+ * @deprecated 建议使用 auth.onAuthStateChange(callback) 替代,监听 SIGNED_IN / SIGNED_OUT 事件。
403
+ */
404
+ public onLoginTypeChanged(callback: Function): void {
405
+ // 使用 Auth 已有的 onAuthStateChange 方法
406
+ this.onAuthStateChange((event) => {
407
+ if (event === AUTH_STATE_CHANGED_TYPE.SIGNED_IN || event === AUTH_STATE_CHANGED_TYPE.SIGNED_OUT) {
408
+ callback.call(this)
409
+ }
410
+ })
411
+ }
412
+
413
+ // ========== 声明子类 Auth 需要提供的方法/属性 ==========
414
+ abstract signIn(params: authModels.SignInRequest): Promise<any>
415
+ abstract signUp(params: authModels.SignUpRequest & { phone?: string }): Promise<SignUpRes>
416
+ abstract signInWithCustomTicket(getTickFn?: authModels.GetCustomSignTicketFn): Promise<any>
417
+ abstract signInAnonymously(params: any): Promise<any>
418
+ abstract getVerification(params: any, options?: any): Promise<authModels.GetVerificationResponse>
419
+ abstract resetPassword(params: authModels.ResetPasswordRequest): Promise<void>
420
+ abstract onLoginStateChanged(callback: Function): Promise<void>
421
+ abstract onAuthStateChange(callback: any): any
422
+ abstract createLoginState(params?: any, options?: any): Promise<any>
423
+ abstract signInWithOAuth(params: any): Promise<any>
424
+ abstract grantProviderToken(params: authModels.GrantProviderTokenRequest): Promise<authModels.GrantProviderTokenResponse>
425
+ abstract bindWithProvider(params: authModels.BindWithProviderRequest): Promise<void>
426
+ protected abstract formatPhone(phone: string): string
427
+ }
428
+
429
+ // ========== User v1 兼容 mixin ==========
430
+
431
+ /**
432
+ * 将 v1 兼容方法应用到 User 类的 prototype 上。
433
+ * 在 index.ts 中调用: applyUserV1Compat(User)
434
+ */
435
+ export function applyUserV1Compat(UserClass: any): void {
436
+ /**
437
+ * v1 API: 将当前账户与自定义登录 Ticket 进行绑定
438
+ * @deprecated v1 兼容 API,当前版本中无法直接实现。
439
+ * 建议使用 auth.signInWithCustomTicket(() => Promise.resolve(ticket)) 替代。
440
+ */
441
+ UserClass.prototype.linkWithTicket = async function (_ticket: string): Promise<void> {
442
+ throw new Error('[v1 兼容] User.linkWithTicket() 在当前版本中无法实现。'
443
+ + ' 建议使用 auth.signInWithCustomTicket(() => Promise.resolve(ticket)) 替代。',)
444
+ }
445
+
446
+ /**
447
+ * v1 API: 将当前账户与第三方鉴权提供方(以重定向形式)进行绑定
448
+ * @deprecated 建议使用 auth.linkIdentity({ provider: 'providerId' }) 替代。
449
+ */
450
+ UserClass.prototype.linkWithRedirect = function (_provider: any): void {
451
+ throw new Error('[v1 兼容] User.linkWithRedirect() 在当前版本中无法实现。'
452
+ + ' 建议使用 auth.linkIdentity({ provider: \'providerId\' }) 替代。',)
453
+ }
454
+
455
+ /**
456
+ * v1 API: 更新当前的登录邮箱
457
+ * @deprecated 建议使用 auth.updateUser({ email: newEmail }) 替代。
458
+ */
459
+ UserClass.prototype.updateEmail = async function (newEmail: string, _password?: string): Promise<void> {
460
+ if (typeof newEmail !== 'string') {
461
+ throwError(ERRORS.INVALID_PARAMS, 'newEmail must be a string')
462
+ }
463
+
464
+ // 使用 User 已有的 updateUserBasicInfo 方法
465
+ await this.updateUserBasicInfo({
466
+ email: newEmail,
467
+ })
468
+ }
469
+
470
+ /**
471
+ * v1 API: 绑定手机号
472
+ * @deprecated v1 兼容 API,当前版本中无法直接在 User 上实现。
473
+ * 建议使用 auth.bindPhoneNumber({ phone_number, verification_token, sudo_token }) 替代。
474
+ */
475
+ UserClass.prototype.linkWithPhoneNumber = async function (_phoneNumber: string, _phoneCode: string): Promise<void> {
476
+ throw new Error('[v1 兼容] User.linkWithPhoneNumber() 在当前版本中无法实现。'
477
+ + ' 建议使用 auth.bindPhoneNumber({ phone_number, verification_token, sudo_token }) 替代。',)
478
+ }
479
+
480
+ /**
481
+ * v1 API: 更新手机号
482
+ * @deprecated v1 兼容 API,当前版本中无法直接在 User 上实现。
483
+ * 建议使用 auth.updateUser({ phone: newPhoneNumber }) 替代。
484
+ */
485
+ UserClass.prototype.updatePhoneNumber = async function (_phoneNumber: string, _phoneCode: string): Promise<void> {
486
+ throw new Error('[v1 兼容] User.updatePhoneNumber() 在当前版本中无法实现。'
487
+ + ' 建议使用 auth.updateUser({ phone: newPhoneNumber }) 替代。',)
488
+ }
489
+
490
+ /**
491
+ * v1 API: 解绑某个登录方式
492
+ * @deprecated v1 兼容 API,当前版本中无法直接在 User 上实现。
493
+ * 建议使用 auth.unlinkIdentity({ provider: loginType }) 替代。
494
+ */
495
+ UserClass.prototype.unlink = async function (_loginType: string): Promise<void> {
496
+ throw new Error('[v1 兼容] User.unlink() 在当前版本中无法实现。'
497
+ + ' 建议使用 auth.unlinkIdentity({ provider: loginType }) 替代。',)
498
+ }
499
+ }