import { SDKAdapterInterface } from '@cloudbase/adapter-interface' import { adapterForWxMp } from './utilities' import type { AuthOptions } from '@cloudbase/oauth' declare const wx: any class OauthClientStorageBase { public readonly localStorage constructor(config?: { localStorage: any /* StorageInterface */; adapter: SDKAdapterInterface }) { try { this.localStorage = config?.localStorage || (config?.adapter?.root?.globalThis || globalThis).localStorage } catch (error) { this.localStorage = config?.localStorage || {} } } /** * Get Item * @param {string} key * @return {Promise} */ async getItem(key: string): Promise { return this.localStorage.getItem(key) } /** * Remove Item. * @param {string} key * @return {Promise} */ async removeItem(key: string) { this.localStorage.removeItem(key) } /** * Set Item. * @param {string} key * @param {string} value * @return {Promise} */ async setItem(key: string, value: string): Promise { this.localStorage.setItem(key, value) } getItemSync(key: string): string | null { return this.localStorage.getItem(key) } setItemSync(key: string, value: string) { this.localStorage.setItem(key, value) } removeItemSync(key: string) { this.localStorage.removeItem(key) } } /** * copy to git@git.woa.com:QBase/lcap/datasource.git/packages/cloud-sdk/common * 同步修改 */ const URL_REG = /^[^:]+:\/\/[^/]+(\/[^?#]+)/ const dealAdapterRequestData = (res, options) => { const requestId = options?.headers?.['x-request-id'] if (!res) { res = {} } if (res?.code || res?.data?.error_code) { res = { error: res.code || res.data.error, error_description: res.data.error_description || res.message || res.code || res.data.error_code, request_id: res.requestId, error_code: res.data?.error_code, } } if (!res?.request_id) { res.request_id = res.request_id || requestId } if (res?.error) { res.error_uri = getUrlPath(options?.url) return res } return res?.data || {} } function generateOauthClientRequest(reqClass) { return async function ( url: string, options?: { body?: any | null headers?: any | null method?: string [key: string]: any }, ) { return new Promise((resolve, reject) => { // Objects must be copied to prevent modification of data such as body. const copyOptions = Object.assign({}, options) if (copyOptions.body && typeof copyOptions.body !== 'string') { copyOptions.body = JSON.stringify(copyOptions.body) } const task = wx.request({ url, data: copyOptions.body, timeout: reqClass._timeout, method: copyOptions.method || 'GET', header: copyOptions.headers, success(res) { res = dealAdapterRequestData(res, { ...options, url }) if (res.error) { reject(res) } resolve(res) }, fail(err) { reject({ error: 'unreachable', error_description: (err as Error).message, }) }, complete(err) { if (!err || !err.errMsg) { return } if (!reqClass._timeout || reqClass._restrictedMethods.indexOf('request') === -1) { return } const { errMsg } = err if (errMsg === 'request:fail timeout') { console.warn(reqClass._timeoutMsg) try { task.abort() } catch (e) {} } }, }) }) } } /** * Get url path. * @param {string} url * @return {string} */ function getUrlPath(url: string) { // return path if matched, or original url return URL_REG.test(url) ? RegExp.$1 : url } /** * 用户自定义adapter处理 * @param config * @param adapter * @returns */ const dealUserAdapter = (config: AuthOptions, adapter: SDKAdapterInterface) => { const data = { ...config } try { if (!config?.storage && adapter?.localStorage) { data.storage = new OauthClientStorageBase({ localStorage: adapter.localStorage, adapter }) } if (!config.captchaOptions && adapter?.captchaOptions) { data.captchaOptions = adapter.captchaOptions } // 用户未传入baseRequest和request时处理 // OAuth2Client优先以 baseRequest 为准 // Auth 在没有 request 的时候会初始化 Captcha,从而使用 Captcha 的 request if (!config.baseRequest && !config.request && adapter?.reqClass) { const reqClassParams = { timeout: 10000, restrictedMethods: ['get', 'post', 'upload', 'download', 'request'], } const adapterReq = new adapter.reqClass(reqClassParams) data.baseRequest = async function (url: string, options?: { method?: string; [key: string]: any }) { let res: any = {} const requestType = options.method?.toLocaleLowerCase() || 'post' const func = adapterReq?.[requestType]?.bind?.(adapterReq) if (typeof func !== 'function') { throw new Error(`invalid 【${requestType}】 request method`) } try { // 兼容非小程序的adapter if ((!options.headers?.['content-type'] || options.headers?.['content-type'].includes('application/json')) && !['string', 'undefined'].includes(typeof options.body)) { options.data = options.data || options.body options.body = JSON.stringify(options.body) } res = await func({ url, ...options }) res = dealAdapterRequestData(res, { ...options, url }) } catch (error) { res = { ...error?.data, ...error } throw res } if (res?.error) { throw res } return res } } } catch (e) { console.error('user adapter generate fail:', e) } return data } /** * 内置默认adapter处理 * @param config * @returns */ const dealAuthAdapter = (config: AuthOptions, adapter: SDKAdapterInterface) => { const data = { ...config } const authOptions: any = {} if (adapter?.captchaOptions) { data.captchaOptions = adapter.captchaOptions } if (!adapterForWxMp.isMatch() || (config.storage && config.baseRequest)) return data try { // eslint-disable-next-line @typescript-eslint/naming-convention const { localStorage, reqClass: ReqClass } = adapterForWxMp.genAdapter({}) if (localStorage) { authOptions.storage = new OauthClientStorageBase({ localStorage, adapter }) } if (ReqClass) { const reqClass = new ReqClass({ timeout: 10000, restrictedMethods: ['get', 'post', 'upload', 'download', 'request'], }) authOptions.request = generateOauthClientRequest(reqClass) } if (data.captchaOptions) { authOptions.baseRequest = authOptions.request authOptions.request = undefined } return { ...data, ...authOptions } } catch (e) { console.error('adapter generate fail:', e) } return data } export const useAuthAdapter = (config: AuthOptions) => { // 兼容微搭cloud-sdk,auth中js-sdk默认的adapter不进行配置 if (config.adapter && (config.adapter as any).type !== 'default') { return dealUserAdapter(config, config.adapter) } return dealAuthAdapter(config, config.adapter) }