@cloudbase/oauth 2.23.3 → 2.24.0
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/dist/cjs/auth/apis.js +170 -14
- package/dist/cjs/auth/auth-error.js +32 -0
- package/dist/cjs/auth/consts.js +24 -2
- package/dist/cjs/auth/models.js +1 -1
- package/dist/cjs/captcha/captcha-dom.js +223 -0
- package/dist/cjs/captcha/captcha.js +11 -102
- package/dist/cjs/index.js +25 -3
- package/dist/cjs/oauth2client/interface.js +1 -1
- package/dist/cjs/oauth2client/models.js +1 -1
- package/dist/cjs/oauth2client/oauth2client.js +384 -110
- package/dist/cjs/utils/base64.js +15 -2
- package/dist/esm/auth/apis.js +113 -2
- package/dist/esm/auth/auth-error.js +9 -0
- package/dist/esm/auth/consts.js +22 -0
- package/dist/esm/captcha/captcha-dom.js +129 -0
- package/dist/esm/captcha/captcha.js +14 -97
- package/dist/esm/index.js +18 -2
- package/dist/esm/oauth2client/oauth2client.js +162 -36
- package/dist/esm/utils/base64.js +12 -0
- package/dist/miniprogram/index.js +1 -1
- package/dist/types/auth/apis.d.ts +19 -1
- package/dist/types/auth/auth-error.d.ts +6 -0
- package/dist/types/auth/consts.d.ts +22 -0
- package/dist/types/auth/models.d.ts +2 -0
- package/dist/types/captcha/captcha-dom.d.ts +3 -0
- package/dist/types/captcha/captcha.d.ts +3 -1
- package/dist/types/index.d.ts +11 -2
- package/dist/types/oauth2client/interface.d.ts +1 -1
- package/dist/types/oauth2client/models.d.ts +14 -0
- package/dist/types/oauth2client/oauth2client.d.ts +58 -2
- package/dist/types/utils/base64.d.ts +5 -0
- package/package.json +4 -4
- package/src/auth/apis.ts +189 -4
- package/src/auth/auth-error.ts +21 -0
- package/src/auth/consts.ts +28 -0
- package/src/auth/models.ts +2 -0
- package/src/captcha/captcha-dom.ts +178 -0
- package/src/captcha/captcha.ts +25 -115
- package/src/index.ts +51 -3
- package/src/oauth2client/interface.ts +1 -1
- package/src/oauth2client/models.ts +28 -0
- package/src/oauth2client/oauth2client.ts +254 -34
- package/src/utils/base64.ts +12 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { CaptchaToken } from '@cloudbase/adapter-interface'
|
|
2
|
+
import { CloudbaseEventEmitter } from '@cloudbase/utilities/dist/cjs/libs/events'
|
|
3
|
+
import { parseCaptcha } from '@cloudbase/utilities/dist/cjs/libs/util'
|
|
4
|
+
import { Auth } from '../auth/apis'
|
|
5
|
+
|
|
6
|
+
// 防抖函数实现
|
|
7
|
+
const debounce = (func: Function, delay: number) => {
|
|
8
|
+
let timeoutId: NodeJS.Timeout
|
|
9
|
+
return (...args: any[]) => {
|
|
10
|
+
clearTimeout(timeoutId)
|
|
11
|
+
timeoutId = setTimeout(() => func.apply(null, args), delay)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const eventBus = new CloudbaseEventEmitter()
|
|
16
|
+
|
|
17
|
+
const CAPTCHA_EVENT = {
|
|
18
|
+
CAPTCHA_DATA_CHANGE: 'captchaDataChange',
|
|
19
|
+
RESOLVE_CAPTCHA_DATA: 'resolveCaptchaData',
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// 图形验证码弹窗样式类
|
|
23
|
+
const CAPTCHA_STYLES = {
|
|
24
|
+
overlay: 'position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999',
|
|
25
|
+
modal:
|
|
26
|
+
'position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:320px;padding:20px;background:#fff;border-radius:8px;box-shadow:0 0 10px rgba(0,0,0,0.2);z-index:1000',
|
|
27
|
+
prompt: 'margin-bottom:15px',
|
|
28
|
+
captchaWrap: 'display:flex;align-items:center;justify-content:space-between;margin-bottom:15px',
|
|
29
|
+
captchaImg: 'width:100%;height:auto;border:1px solid #eee',
|
|
30
|
+
refreshBtn:
|
|
31
|
+
'padding:10px 8px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer;flex:1;white-space:nowrap;margin-left:10px',
|
|
32
|
+
input:
|
|
33
|
+
'width:100%;padding:14px 16px;margin-bottom:15px;box-sizing:border-box;border:2px solid #e8ecf4;border-radius:10px;font-size:15px',
|
|
34
|
+
confirmBtn: 'width:100%;padding:10px;background:#007bff;color:#fff;border:none;border-radius:4px;cursor:pointer',
|
|
35
|
+
loading: 'background:#6c757d;cursor:not-allowed',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 显示图形图形验证码
|
|
40
|
+
* @param param0
|
|
41
|
+
*/
|
|
42
|
+
const genCaptchaDom = ({ oauthInstance, captchaState }) => {
|
|
43
|
+
const CAPTCHA_IMG_ID = 'captcha-image'
|
|
44
|
+
|
|
45
|
+
// 刷新图形验证码函数
|
|
46
|
+
const refreshCaptcha = async () => {
|
|
47
|
+
try {
|
|
48
|
+
const result = await oauthInstance.createCaptchaData({ state: captchaState.state })
|
|
49
|
+
captchaState = { ...captchaState, captchaData: result.data, token: result.token }
|
|
50
|
+
;(document.getElementById(CAPTCHA_IMG_ID) as HTMLImageElement).src = result.data
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error('刷新图形验证码失败', error)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 创建元素并设置样式
|
|
57
|
+
const createElement = (tag: string, styles?: string, text?: string) => {
|
|
58
|
+
const el = document.createElement(tag)
|
|
59
|
+
if (styles) el.style.cssText = styles
|
|
60
|
+
if (text) el.textContent = text
|
|
61
|
+
return el
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 创建遮罩层和弹窗
|
|
65
|
+
const overlay = createElement('div', CAPTCHA_STYLES.overlay)
|
|
66
|
+
const modal = createElement('div', CAPTCHA_STYLES.modal)
|
|
67
|
+
|
|
68
|
+
// 添加提示文字
|
|
69
|
+
modal.appendChild(createElement('p', CAPTCHA_STYLES.prompt, '请输入你看到的字符:'))
|
|
70
|
+
|
|
71
|
+
// 图形验证码区域
|
|
72
|
+
const captchaWrap = createElement('div', CAPTCHA_STYLES.captchaWrap)
|
|
73
|
+
const captchaImg = createElement('img', CAPTCHA_STYLES.captchaImg) as HTMLImageElement
|
|
74
|
+
captchaImg.id = CAPTCHA_IMG_ID
|
|
75
|
+
captchaImg.src = captchaState.captchaData
|
|
76
|
+
captchaWrap.appendChild(captchaImg)
|
|
77
|
+
|
|
78
|
+
// 刷新按钮
|
|
79
|
+
const refreshBtn = createElement('button', CAPTCHA_STYLES.refreshBtn, '刷新') as HTMLButtonElement
|
|
80
|
+
refreshBtn.onclick = debounce(async () => {
|
|
81
|
+
refreshBtn.textContent = '加载中...'
|
|
82
|
+
refreshBtn.disabled = true
|
|
83
|
+
refreshBtn.style.cssText = `${CAPTCHA_STYLES.refreshBtn};${CAPTCHA_STYLES.loading}`
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await refreshCaptcha()
|
|
87
|
+
} finally {
|
|
88
|
+
refreshBtn.textContent = '刷新'
|
|
89
|
+
refreshBtn.disabled = false
|
|
90
|
+
refreshBtn.style.cssText = CAPTCHA_STYLES.refreshBtn
|
|
91
|
+
}
|
|
92
|
+
}, 500)
|
|
93
|
+
captchaWrap.appendChild(refreshBtn)
|
|
94
|
+
modal.appendChild(captchaWrap)
|
|
95
|
+
|
|
96
|
+
// 输入框
|
|
97
|
+
const input = createElement('input', CAPTCHA_STYLES.input) as HTMLInputElement
|
|
98
|
+
input.type = 'text'
|
|
99
|
+
input.placeholder = '输入字符'
|
|
100
|
+
input.maxLength = 4
|
|
101
|
+
modal.appendChild(input)
|
|
102
|
+
|
|
103
|
+
// 错误提示元素
|
|
104
|
+
const errorMsg = createElement(
|
|
105
|
+
'div',
|
|
106
|
+
'color:#dc3545;font-size:12px;margin-bottom:10px;display:none;padding:8px;background:#f8d7da;border:1px solid #f5c6cb;border-radius:4px',
|
|
107
|
+
)
|
|
108
|
+
modal.appendChild(errorMsg)
|
|
109
|
+
|
|
110
|
+
// 显示错误提示
|
|
111
|
+
const showError = (message: string) => {
|
|
112
|
+
errorMsg.textContent = message
|
|
113
|
+
errorMsg.style.display = 'block'
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 确定按钮
|
|
117
|
+
const confirmBtn = createElement('button', CAPTCHA_STYLES.confirmBtn, '确定') as HTMLButtonElement
|
|
118
|
+
confirmBtn.onclick = debounce(async () => {
|
|
119
|
+
const inputValue = input.value.trim()
|
|
120
|
+
if (!inputValue) {
|
|
121
|
+
showError('请输入字符')
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 隐藏之前的错误提示
|
|
126
|
+
errorMsg.style.display = 'none'
|
|
127
|
+
|
|
128
|
+
confirmBtn.textContent = '验证中...'
|
|
129
|
+
confirmBtn.disabled = true
|
|
130
|
+
confirmBtn.style.cssText = `${CAPTCHA_STYLES.confirmBtn};${CAPTCHA_STYLES.loading}`
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const verifyResult = await oauthInstance.verifyCaptchaData({
|
|
134
|
+
token: captchaState.token,
|
|
135
|
+
key: inputValue,
|
|
136
|
+
})
|
|
137
|
+
eventBus.fire(CAPTCHA_EVENT.RESOLVE_CAPTCHA_DATA, verifyResult)
|
|
138
|
+
console.log('图形验证码校验成功')
|
|
139
|
+
document.body.removeChild(overlay)
|
|
140
|
+
document.body.removeChild(modal)
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('图形验证码校验失败', error)
|
|
143
|
+
// 根据错误类型显示不同的错误提示
|
|
144
|
+
const errorMessage = error.error_description || '图形验证码校验失败'
|
|
145
|
+
showError(errorMessage)
|
|
146
|
+
|
|
147
|
+
// 清空输入框并刷新图形验证码
|
|
148
|
+
input.value = ''
|
|
149
|
+
input.focus()
|
|
150
|
+
await refreshCaptcha()
|
|
151
|
+
} finally {
|
|
152
|
+
confirmBtn.textContent = '确定'
|
|
153
|
+
confirmBtn.disabled = false
|
|
154
|
+
confirmBtn.style.cssText = CAPTCHA_STYLES.confirmBtn
|
|
155
|
+
}
|
|
156
|
+
}, 500)
|
|
157
|
+
modal.appendChild(confirmBtn)
|
|
158
|
+
|
|
159
|
+
// 插入页面
|
|
160
|
+
document.body.appendChild(overlay)
|
|
161
|
+
document.body.appendChild(modal)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export const openURIWithCallback = async (url: string, oauthInstance?: Auth): Promise<CaptchaToken> => {
|
|
165
|
+
// 解析URL中的图形验证码参数
|
|
166
|
+
const { captchaData, state, token } = parseCaptcha(url)
|
|
167
|
+
|
|
168
|
+
genCaptchaDom({ oauthInstance, captchaState: { captchaData, state, token } })
|
|
169
|
+
|
|
170
|
+
// 监听图形验证码校验结果
|
|
171
|
+
return new Promise((resolve) => {
|
|
172
|
+
console.log('等待图形验证码校验结果...')
|
|
173
|
+
eventBus.on(CAPTCHA_EVENT.RESOLVE_CAPTCHA_DATA, (res) => {
|
|
174
|
+
// auth.verifyCaptchaData的校验结果
|
|
175
|
+
resolve(res?.data)
|
|
176
|
+
})
|
|
177
|
+
})
|
|
178
|
+
}
|
package/src/captcha/captcha.ts
CHANGED
|
@@ -2,17 +2,20 @@ import { ApiUrls, ErrorType } from '../auth/consts'
|
|
|
2
2
|
import { SimpleStorage, RequestFunction } from '../oauth2client/interface'
|
|
3
3
|
import { AuthClientRequestOptions } from '../oauth2client/models'
|
|
4
4
|
import { defaultStorage } from '../oauth2client/oauth2client'
|
|
5
|
-
import {
|
|
6
|
-
import MyURLSearchParams from '../utils/urlSearchParams'
|
|
5
|
+
import { isMp } from '../utils/mp'
|
|
7
6
|
import { SDKAdapterInterface } from '@cloudbase/adapter-interface'
|
|
7
|
+
import { openURIWithCallback } from './captcha-dom'
|
|
8
|
+
import { Auth } from '../auth/apis'
|
|
8
9
|
|
|
9
10
|
export interface CaptchaOptions {
|
|
11
|
+
env: string;
|
|
10
12
|
clientId: string
|
|
11
13
|
request: RequestFunction
|
|
12
14
|
storage: SimpleStorage
|
|
13
15
|
// 打开网页并通过URL回调获取 CaptchaToken,针对不通的平台,该函数可以自定义实现, 默认集成浏览器端认证
|
|
14
16
|
openURIWithCallback?: OpenURIWithCallbackFuction
|
|
15
17
|
adapter?: SDKAdapterInterface & { isMatch?: () => boolean }
|
|
18
|
+
oauthInstance?: Auth
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
type OpenURIWithCallbackFuction = (url: string) => Promise<CaptchaToken>
|
|
@@ -50,7 +53,7 @@ export class Captcha {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
this.config = opts
|
|
53
|
-
this.tokenSectionName = `captcha_${opts.clientId}`
|
|
56
|
+
this.tokenSectionName = `captcha_${opts.clientId || opts.env}`
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
public isMatch() {
|
|
@@ -89,75 +92,9 @@ export class Captcha {
|
|
|
89
92
|
}
|
|
90
93
|
|
|
91
94
|
private getDefaultOpenURIWithCallback(): OpenURIWithCallbackFuction {
|
|
92
|
-
|
|
93
|
-
if (window.location.search.indexOf('__captcha') > 0) {
|
|
94
|
-
document.body.style.display = 'none'
|
|
95
|
-
}
|
|
96
|
-
if (document.getElementById('captcha_panel_wrap') === null) {
|
|
97
|
-
const elementDiv = document.createElement('div')
|
|
98
|
-
elementDiv.style.cssText = 'background-color: rgba(0, 0, 0, 0.7);position: fixed;left: 0px;right: 0px;top: 0px;bottom: 0px;padding: 9vw 0 0 0;display: none;z-index:100;'
|
|
99
|
-
elementDiv.setAttribute('id', 'captcha_panel_wrap')
|
|
100
|
-
setTimeout(() => {
|
|
101
|
-
document.body.appendChild(elementDiv)
|
|
102
|
-
}, 0)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return this.defaultOpenURIWithCallback
|
|
95
|
+
return (url: string) => openURIWithCallback(url, this.config.oauthInstance)
|
|
106
96
|
}
|
|
107
97
|
|
|
108
|
-
/**
|
|
109
|
-
* 默认通过浏览器打开网页并获取回调
|
|
110
|
-
*/
|
|
111
|
-
private async defaultOpenURIWithCallback(
|
|
112
|
-
url: string,
|
|
113
|
-
opts?: { width?: string; height?: string },
|
|
114
|
-
): Promise<CaptchaToken> {
|
|
115
|
-
const { width = '355px', height = '355px' } = opts || {}
|
|
116
|
-
|
|
117
|
-
const matched = url.match(/^(data:.*)$/)
|
|
118
|
-
if (matched) {
|
|
119
|
-
return Promise.reject({
|
|
120
|
-
error: ErrorType.UNIMPLEMENTED,
|
|
121
|
-
error_description: 'need to impl captcha data',
|
|
122
|
-
})
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const target = document.getElementById('captcha_panel_wrap')
|
|
126
|
-
const iframe = document.createElement('iframe')
|
|
127
|
-
target.innerHTML = ''
|
|
128
|
-
|
|
129
|
-
iframe.setAttribute('src', url)
|
|
130
|
-
iframe.setAttribute('id', 'review-panel-iframe')
|
|
131
|
-
iframe.style.cssText = `min-width:${width};display:block;height:${height};margin:0 auto;background-color: rgb(255, 255, 255);border: none;`
|
|
132
|
-
target.appendChild(iframe)
|
|
133
|
-
target.style.display = 'block'
|
|
134
|
-
return new Promise<CaptchaToken>((resolve, reject) => {
|
|
135
|
-
iframe.onload = function () {
|
|
136
|
-
try {
|
|
137
|
-
const windowLocation = window.location
|
|
138
|
-
const iframeLocation = iframe.contentWindow.location
|
|
139
|
-
if (iframeLocation.host + iframeLocation.pathname === windowLocation.host + windowLocation.pathname) {
|
|
140
|
-
target.style.display = 'none'
|
|
141
|
-
const iframeUrlParams = new MyURLSearchParams(iframeLocation.search)
|
|
142
|
-
const captchToken = iframeUrlParams.get('captcha_token')
|
|
143
|
-
if (captchToken) {
|
|
144
|
-
return resolve({
|
|
145
|
-
captcha_token: captchToken,
|
|
146
|
-
expires_in: Number(iframeUrlParams.get('expires_in')),
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
return reject({
|
|
150
|
-
error: iframeUrlParams.get('error'),
|
|
151
|
-
error_description: iframeUrlParams.get('error_description'),
|
|
152
|
-
})
|
|
153
|
-
}
|
|
154
|
-
target.style.display = 'block'
|
|
155
|
-
} catch (error) {
|
|
156
|
-
target.style.display = 'block'
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
}
|
|
161
98
|
/**
|
|
162
99
|
* getCaptchaToken 获取captchaToken
|
|
163
100
|
*/
|
|
@@ -169,53 +106,26 @@ export class Captcha {
|
|
|
169
106
|
return captchaToken
|
|
170
107
|
}
|
|
171
108
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
captcha_token?: string
|
|
175
|
-
expires_in?: number
|
|
176
|
-
}
|
|
177
|
-
if (!this.isMatch() && !isInMpWebView()) {
|
|
178
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
179
|
-
const redirect_uri = `${window.location.origin + window.location.pathname}?__captcha=on`
|
|
180
|
-
captchaTokenResp = await this.config.request<GetCaptchaResponse>(ApiUrls.GET_CAPTCHA_URL, {
|
|
181
|
-
method: 'POST',
|
|
182
|
-
body: {
|
|
183
|
-
client_id: this.config.clientId,
|
|
184
|
-
redirect_uri,
|
|
185
|
-
state,
|
|
186
|
-
},
|
|
187
|
-
withCredentials: false,
|
|
188
|
-
})
|
|
189
|
-
if (captchaTokenResp.captcha_token) {
|
|
190
|
-
const captchaToken = {
|
|
191
|
-
captcha_token: captchaTokenResp.captcha_token,
|
|
192
|
-
expires_in: captchaTokenResp.expires_in,
|
|
193
|
-
}
|
|
194
|
-
this.saveCaptchaToken(captchaToken)
|
|
195
|
-
return captchaTokenResp.captcha_token
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
/**
|
|
109
|
+
|
|
110
|
+
/**
|
|
199
111
|
* https://iwiki.woa.com/p/4010699417
|
|
200
112
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
}
|
|
218
|
-
const captchaToken = await this.config.openURIWithCallback(captchaTokenResp.url)
|
|
113
|
+
const captchaDataResp = await this.config.request<{
|
|
114
|
+
data: string
|
|
115
|
+
type: 'image'
|
|
116
|
+
token: string
|
|
117
|
+
expires_in: number
|
|
118
|
+
}>(ApiUrls.CAPTCHA_DATA_URL, {
|
|
119
|
+
method: 'POST',
|
|
120
|
+
body: {
|
|
121
|
+
state,
|
|
122
|
+
redirect_uri: '',
|
|
123
|
+
},
|
|
124
|
+
withCredentials: false,
|
|
125
|
+
})
|
|
126
|
+
const captchaTokenUrl = `${captchaDataResp.data}?state=${encodeURIComponent(state)}&token=${encodeURIComponent(captchaDataResp.token,)}`
|
|
127
|
+
|
|
128
|
+
const captchaToken = await this.config.openURIWithCallback(captchaTokenUrl)
|
|
219
129
|
this.saveCaptchaToken(captchaToken)
|
|
220
130
|
return captchaToken.captcha_token
|
|
221
131
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,20 +1,44 @@
|
|
|
1
1
|
import { OAuth2Client } from './oauth2client/oauth2client'
|
|
2
2
|
import { AuthOptions, Auth } from './auth/apis'
|
|
3
3
|
import { AUTH_API_PREFIX } from './auth/consts'
|
|
4
|
+
import { Credentials } from './oauth2client/models'
|
|
4
5
|
export { Auth } from './auth/apis'
|
|
5
|
-
export { AUTH_API_PREFIX } from './auth/consts'
|
|
6
|
+
export { AUTH_API_PREFIX, OAUTH_TYPE } from './auth/consts'
|
|
7
|
+
export { AuthError } from './auth/auth-error'
|
|
6
8
|
export * as authModels from './auth/models'
|
|
7
|
-
export type { ProviderProfile } from './auth/models'
|
|
9
|
+
export type { ProviderProfile, UserInfo, ModifyUserBasicInfoRequest } from './auth/models'
|
|
8
10
|
export type { Credentials, OAuth2ClientOptions, ResponseError, AuthClientRequestOptions } from './oauth2client/models'
|
|
9
11
|
export type { AuthOptions } from './auth/apis'
|
|
12
|
+
export { weappJwtDecodeAll } from './utils/base64'
|
|
13
|
+
export { LOGIN_STATE_CHANGED_TYPE, EVENTS, AUTH_STATE_CHANGED_TYPE } from './auth/consts'
|
|
10
14
|
|
|
11
15
|
export class CloudbaseOAuth {
|
|
12
16
|
public oauth2client: OAuth2Client
|
|
13
17
|
public authApi: Auth
|
|
18
|
+
private detectSessionInUrl: boolean
|
|
14
19
|
|
|
15
20
|
constructor(authOptions: AuthOptions) {
|
|
16
21
|
// eslint-disable-next-line max-len
|
|
17
|
-
const {
|
|
22
|
+
const {
|
|
23
|
+
apiOrigin,
|
|
24
|
+
apiPath = AUTH_API_PREFIX,
|
|
25
|
+
clientId,
|
|
26
|
+
env,
|
|
27
|
+
storage,
|
|
28
|
+
request,
|
|
29
|
+
baseRequest,
|
|
30
|
+
anonymousSignInFunc,
|
|
31
|
+
wxCloud,
|
|
32
|
+
adapter,
|
|
33
|
+
onCredentialsError,
|
|
34
|
+
headers,
|
|
35
|
+
i18n,
|
|
36
|
+
useWxCloud,
|
|
37
|
+
eventBus,
|
|
38
|
+
detectSessionInUrl,
|
|
39
|
+
debug,
|
|
40
|
+
} = authOptions
|
|
41
|
+
this.detectSessionInUrl = detectSessionInUrl ?? false
|
|
18
42
|
this.oauth2client = new OAuth2Client({
|
|
19
43
|
apiOrigin,
|
|
20
44
|
apiPath,
|
|
@@ -28,6 +52,8 @@ export class CloudbaseOAuth {
|
|
|
28
52
|
headers: headers || {},
|
|
29
53
|
i18n,
|
|
30
54
|
useWxCloud,
|
|
55
|
+
eventBus,
|
|
56
|
+
debug,
|
|
31
57
|
})
|
|
32
58
|
|
|
33
59
|
this.authApi = new Auth({
|
|
@@ -37,5 +63,27 @@ export class CloudbaseOAuth {
|
|
|
37
63
|
request: request ? this.oauth2client.request.bind(this.oauth2client) : undefined,
|
|
38
64
|
adapter,
|
|
39
65
|
})
|
|
66
|
+
|
|
67
|
+
// Set the getInitialSession callback after Auth is created
|
|
68
|
+
// This allows Auth.getInitialSession to handle URL detection and OAuth verification
|
|
69
|
+
if (detectSessionInUrl) {
|
|
70
|
+
this.oauth2client.setGetInitialSession(this.authApi.getInitialSession.bind(this.authApi))
|
|
71
|
+
}
|
|
72
|
+
// Note: Do NOT auto-call initialize() here.
|
|
73
|
+
// Upper layer (packages/auth) should call initializeSession() after setting up onInitialSessionObtained callback
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Setup the onInitialSessionObtained callback and trigger initialization.
|
|
78
|
+
* This should be called by upper layer (packages/auth) after creating Auth instance.
|
|
79
|
+
* @param onUserObtained Callback to handle user info storage after session is obtained
|
|
80
|
+
*/
|
|
81
|
+
public initializeSession(onUserObtained?: (data: { session: Credentials; user?: any }) => void | Promise<void>): void {
|
|
82
|
+
if (!this.detectSessionInUrl) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
this.oauth2client.setOnInitialSessionObtained(onUserObtained)
|
|
86
|
+
// Pass callback directly to initialize() to ensure it's set before initialization starts
|
|
87
|
+
this.oauth2client.initialize()
|
|
40
88
|
}
|
|
41
89
|
}
|
|
@@ -15,7 +15,7 @@ export abstract class AuthClient {
|
|
|
15
15
|
/**
|
|
16
16
|
* Sets the auth credentials.
|
|
17
17
|
*/
|
|
18
|
-
abstract setCredentials(credentials?: Credentials): void
|
|
18
|
+
abstract setCredentials(credentials?: Credentials): Promise<void>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* get the current accessToken from AuthClient, you can use this to detect login status
|
|
@@ -46,6 +46,12 @@ export interface AuthClientRequestOptions extends RequestOptions {
|
|
|
46
46
|
withBasicAuth?: boolean;
|
|
47
47
|
retry?: number;
|
|
48
48
|
useWxCloud?: boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Custom getCredentials function for this request.
|
|
51
|
+
* When provided, uses this function instead of the default getCredentials() call
|
|
52
|
+
* to avoid potential deadlocks during initialization.
|
|
53
|
+
*/
|
|
54
|
+
getCredentials?: () => Credentials | Promise<Credentials | null> | null;
|
|
49
55
|
|
|
50
56
|
[key: string]: any;
|
|
51
57
|
}
|
|
@@ -72,4 +78,26 @@ export interface OAuth2ClientOptions {
|
|
|
72
78
|
onCredentialsError?: AuthOptions['onCredentialsError']
|
|
73
79
|
i18n?: ICloudbaseConfig['i18n']
|
|
74
80
|
useWxCloud?: boolean
|
|
81
|
+
eventBus?: any
|
|
82
|
+
/**
|
|
83
|
+
* Enable debug logging
|
|
84
|
+
*/
|
|
85
|
+
debug?: boolean
|
|
86
|
+
/**
|
|
87
|
+
* Callback function to get initial session (e.g., from OAuth callback in URL).
|
|
88
|
+
* Called by OAuth2Client.initialize() if set.
|
|
89
|
+
* The callback should handle URL detection, OAuth verification, and return credentials.
|
|
90
|
+
* Returns { data: { session: Credentials, user?: any }, error: null } on success,
|
|
91
|
+
* or { data: null, error: Error } on failure.
|
|
92
|
+
*/
|
|
93
|
+
getInitialSession?: () => Promise<{
|
|
94
|
+
data: { session: Credentials; user?: any } | null
|
|
95
|
+
error: Error | null
|
|
96
|
+
}>
|
|
97
|
+
/**
|
|
98
|
+
* Callback invoked after initial session is obtained and stored.
|
|
99
|
+
* Used to let upper layer (e.g., packages/auth) handle user info storage.
|
|
100
|
+
* Called with session and user data.
|
|
101
|
+
*/
|
|
102
|
+
onInitialSessionObtained?: (data: { session: Credentials; user?: any }) => void | Promise<void>
|
|
75
103
|
}
|