2026-07-17 13:50:30 +08:00
import {
_notifyInitialized ,
_setUserInfo ,
getUserId as getCommonUserId ,
getUserInfo as getCommonUserInfo ,
initConfigFromRemote ,
isInitialized ,
type XuqmInitOptions ,
type XuqmLoginOptions ,
type XuqmUserInfo ,
} from './config'
2026-06-15 10:57:55 +08:00
import { DEFAULT_TENANT_PLATFORM_URL } from './constants'
2026-07-17 13:50:30 +08:00
import { _clearAccessToken , _saveAccessToken , configureHttp } from './http'
import { decryptConfigFile } from './crypto'
2026-04-24 16:16:31 +08:00
2026-05-22 17:57:01 +08:00
let _initPromise : Promise < void > | null = null
2026-07-17 13:50:30 +08:00
let _configFileInitPromise : Promise < void > | null = null
let _initializationKey : string | null = null
let _sessionTransition : Promise < void > = Promise . resolve ( )
let _activeSessionKey : string | null = null
2026-05-22 17:57:01 +08:00
2026-07-17 13:50:30 +08:00
function initializationKey ( options : XuqmInitOptions ) : string {
return ` ${ options . appKey . trim ( ) } \ n ${ ( options . platformUrl ? ? DEFAULT_TENANT_PLATFORM_URL ) . replace ( /\/$/ , '' ) } `
2026-05-22 17:57:01 +08:00
}
2026-07-17 13:50:30 +08:00
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 ( )
2026-05-22 17:57:01 +08:00
}
2026-07-17 13:50:30 +08:00
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
2026-05-22 17:57:01 +08:00
}
2026-07-17 13:50:30 +08:00
function enqueueSessionTransition ( action : ( ) = > Promise < void > ) : Promise < void > {
const next = _sessionTransition . catch ( ( ) = > undefined ) . then ( action )
_sessionTransition = next
return next
2026-06-15 10:04:37 +08:00
}
2026-04-24 16:16:31 +08:00
export const XuqmSDK = {
/ * *
2026-06-15 10:57:55 +08:00
* 方 式 B : 手 动 初 始 化 。
*
* 请 求 平 台 获 取 该 appKey 的 完 整 服 务 配 置 ( IM 、 Push 、 文 件 服 务 等 URL 及 开 通 状 态 ) 。
* 失 败 时 直 接 抛 出 , 不 降 级 。
*
* @param options . appKey 应 用 标 识
* @param options . platformUrl 平 台 地 址 ; 不 传 则 使 用 内 置 默 认 公 有 平 台 地 址
* @param options . debug 是 否 开 启 调 试 日 志
2026-04-25 16:41:19 +08:00
* /
async initialize ( options : XuqmInitOptions ) : Promise < void > {
2026-07-17 13:50:30 +08:00
if ( ! options . appKey . trim ( ) ) {
throw new Error ( '[XuqmSDK] appKey is required.' )
2026-06-19 01:27:56 +08:00
}
2026-07-17 13:50:30 +08:00
await startInitialization ( options )
2026-06-15 01:44:20 +08:00
} ,
2026-05-22 17:57:01 +08:00
/ * *
2026-06-15 10:57:55 +08:00
* 等 待 初 始 化 完 成 。 在 任 意 子 SDK 内 部 调 用 以 确 保 XuqmSDK 已 就 绪 。
2026-05-22 17:57:01 +08:00
* /
async awaitInitialization ( ) : Promise < void > {
2026-07-17 13:50:30 +08:00
await awaitInitialization ( )
2026-04-24 16:16:31 +08:00
} ,
2026-04-28 16:55:12 +08:00
getUserId ( ) : string | null {
return getCommonUserId ( )
} ,
2026-06-15 01:44:20 +08:00
/ * *
2026-07-17 13:50:30 +08:00
* 用 户 认 证 核 心 枢 纽 。 登 录 成 功 后 调 用 一 次 , 所 有 已 安 装 的 扩 展 SDK 自 动 同 步 状 态 。
2026-06-15 10:57:55 +08:00
* 登 出 时 传 null , 触 发 所 有 子 SDK 登 出 。
2026-06-15 01:44:20 +08:00
* /
2026-07-17 13:50:30 +08:00
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
} )
2026-06-15 01:44:20 +08:00
} ,
getUserInfo ( ) : XuqmUserInfo | null {
return getCommonUserInfo ( )
} ,
2026-04-24 16:16:31 +08:00
}
2026-05-22 17:57:01 +08:00
export async function awaitInitialization ( ) : Promise < void > {
if ( isInitialized ( ) ) return
2026-07-17 13:50:30 +08:00
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
2026-05-22 17:57:01 +08:00
}
2026-06-19 01:27:56 +08:00
2026-07-17 13:50:30 +08:00
/** 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
2026-06-19 01:27:56 +08:00
}