XuqmGroup-RNSDK/packages/common/src/sdk.ts

180 行
6.0 KiB
TypeScript

import {
_notifyInitialized,
_setUserInfo,
getUserId as getCommonUserId,
getUserInfo as getCommonUserInfo,
initConfigFromRemote,
isInitialized,
type XuqmInitOptions,
type XuqmLoginOptions,
type XuqmUserInfo,
} from './config'
import { DEFAULT_TENANT_PLATFORM_URL } from './constants'
import { _clearAccessToken, _saveAccessToken, configureHttp } from './http'
import { decryptConfigFile } from './crypto'
let _initPromise: Promise<void> | null = null
let _configFileInitPromise: Promise<void> | null = null
let _initializationKey: string | null = null
let _sessionTransition: Promise<void> = Promise.resolve()
let _activeSessionKey: string | null = null
function initializationKey(options: XuqmInitOptions): string {
return `${options.appKey.trim()}\n${(options.platformUrl ?? DEFAULT_TENANT_PLATFORM_URL).replace(/\/$/, '')}`
}
function startInitialization(options: XuqmInitOptions, signingKey?: string): Promise<void> {
const nextKey = initializationKey(options)
if (isInitialized()) {
if (_initializationKey !== nextKey) {
return Promise.reject(new Error('[XuqmSDK] Already initialized with a different config.'))
}
return Promise.resolve()
}
if (_initPromise) {
if (_initializationKey !== nextKey) {
return Promise.reject(
new Error('[XuqmSDK] Initialization is already running with a different config.'),
)
}
return _initPromise
}
_initializationKey = nextKey
_initPromise = (async () => {
const baseUrl = options.platformUrl ?? DEFAULT_TENANT_PLATFORM_URL
const configUrl = `${baseUrl.replace(/\/$/, '')}/api/sdk/config?appKey=${encodeURIComponent(options.appKey)}`
const res = await fetch(configUrl)
if (!res.ok) {
throw new Error(`[XuqmSDK] Platform config request failed: ${res.status}`)
}
const json = (await res.json()) as Record<string, unknown>
const remote = (json.data ?? json) as Record<string, unknown>
initConfigFromRemote(
options,
{
apiUrl: remote.apiUrl as string | undefined,
imWsUrl: remote.imWsUrl as string | undefined,
fileServiceUrl: remote.fileServiceUrl as string | undefined,
imEnabled: remote.imEnabled as boolean | undefined,
pushEnabled: remote.pushEnabled as boolean | undefined,
bugCollectApiUrl: remote.bugCollectApiUrl as string | undefined,
bugCollectEnabled: remote.bugCollectEnabled as boolean | undefined,
},
signingKey,
)
configureHttp({
baseUrl: (remote.apiUrl as string | undefined) ?? baseUrl,
debug: options.debug,
})
await _notifyInitialized()
})().catch(error => {
_initPromise = null
_initializationKey = null
throw error
})
return _initPromise
}
function enqueueSessionTransition(action: () => Promise<void>): Promise<void> {
const next = _sessionTransition.catch(() => undefined).then(action)
_sessionTransition = next
return next
}
export const XuqmSDK = {
/**
* B
*
* appKey IMPush URL
*
*
* @param options.appKey
* @param options.platformUrl 使
* @param options.debug
*/
async initialize(options: XuqmInitOptions): Promise<void> {
if (!options.appKey.trim()) {
throw new Error('[XuqmSDK] appKey is required.')
}
await startInitialization(options)
},
/**
* SDK XuqmSDK
*/
async awaitInitialization(): Promise<void> {
await awaitInitialization()
},
getUserId(): string | null {
return getCommonUserId()
},
/**
* SDK
* null SDK
*/
async login(options: XuqmLoginOptions): Promise<void> {
const userId = options.userId.trim()
if (!userId) throw new Error('[XuqmSDK] userId is required.')
const accessToken = options.accessToken?.trim() || undefined
const normalized = { ...options, accessToken, userId }
const nextSessionKey = JSON.stringify(normalized)
await enqueueSessionTransition(async () => {
await awaitInitialization()
if (_activeSessionKey === nextSessionKey && getCommonUserInfo()) return
if (accessToken) await _saveAccessToken(accessToken)
else await _clearAccessToken()
const { accessToken: _accessToken, ...userInfo } = normalized
await _setUserInfo(userInfo)
_activeSessionKey = nextSessionKey
})
},
async logout(): Promise<void> {
await enqueueSessionTransition(async () => {
await _clearAccessToken()
if (getCommonUserInfo()) await _setUserInfo(null)
_activeSessionKey = null
})
},
getUserInfo(): XuqmUserInfo | null {
return getCommonUserInfo()
},
}
export async function awaitInitialization(): Promise<void> {
if (isInitialized()) return
const pendingInitialization = _configFileInitPromise ?? _initPromise
if (!pendingInitialization) {
throw new Error(
'[XuqmSDK] Automatic initialization did not start. Place config.xuqmconfig in src/assets/config and wrap Metro with withXuqmConfig(), or call XuqmSDK.initialize().',
)
}
await pendingInitialization
}
/** Internal entry used only by the Metro auto-init module. */
export function _initializeFromConfigFile(
encryptedContent: string,
options?: { debug?: boolean },
): Promise<void> {
if (_configFileInitPromise) return _configFileInitPromise
_configFileInitPromise = (async () => {
const file = await decryptConfigFile(encryptedContent)
const platformUrl = file.serverUrl ?? file.baseUrl ?? undefined
await startInitialization(
{ appKey: file.appKey, platformUrl, debug: options?.debug },
file.signingKey,
)
})().catch(error => {
_configFileInitPromise = null
throw error
})
return _configFileInitPromise
}