import { initConfigFromRemote, isInitialized, type XuqmInitOptions, type XuqmUserInfo, setUserInfo as setCommonUserInfo, getUserInfo as getCommonUserInfo, getUserId as getCommonUserId } from './config' import { DEFAULT_TENANT_PLATFORM_URL } from './constants' import { configureHttp } from './http' import { decryptConfigFile } from './configCrypto' let _initPromise: Promise | null = null let _initResolve: (() => void) | null = null let _initReject: ((e: unknown) => void) | null = null function ensureInitPromise(): Promise { if (!_initPromise) { _initPromise = new Promise((resolve, reject) => { _initResolve = resolve _initReject = reject }) } return _initPromise } function markInitialized(): void { if (_initResolve) { _initResolve() _initResolve = null _initReject = null } } function markInitializationFailed(e: unknown): void { const reject = _initReject // Reset so a retry can call initialize() again _initPromise = null _initResolve = null _initReject = null reject?.(e) } export const XuqmSDK = { /** * 方式 B:手动初始化。 * * 请求平台获取该 appKey 的完整服务配置(IM、Push、文件服务等 URL 及开通状态)。 * 失败时直接抛出,不降级。 * * @param options.appKey 应用标识 * @param options.platformUrl 平台地址;不传则使用内置默认公有平台地址 * @param options.debug 是否开启调试日志 */ async initialize(options: XuqmInitOptions): Promise { if (isInitialized()) return ensureInitPromise() const baseUrl = options.platformUrl ?? DEFAULT_TENANT_PLATFORM_URL const configUrl = `${baseUrl}/api/sdk/config?appKey=${options.appKey}` try { 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 const remote = (json.data ?? json) as Record initConfigFromRemote(options, { apiUrl: remote.apiUrl as string | undefined, imWsUrl: remote.imWsUrl as string | undefined, fileServiceUrl: remote.fileServiceUrl as string | undefined, licenseUrl: remote.licenseUrl as string | undefined, imEnabled: remote.imEnabled as boolean | undefined, pushEnabled: remote.pushEnabled as boolean | undefined, licenseEnabled: remote.licenseEnabled as boolean | undefined, }) configureHttp({ baseUrl: (remote.apiUrl as string | undefined) ?? baseUrl, debug: options.debug, }) markInitialized() } catch (e) { markInitializationFailed(e) throw e } }, /** * 方式 A(内部):从加密配置文件初始化 SDK。 * * 宿主把 .xuqmconfig 文件内容通过 autoInit 模块传入,SDK 自动解密并调用 initialize()。 * 不对外暴露(不在 index.ts 中 export)。 * * @param encryptedContent 加密配置文件的完整内容(XUQM-CONFIG-V1 或 XUQM-LICENSE-V1 格式) */ async initWithConfigFile(encryptedContent: string, options?: { debug?: boolean }): Promise { if (isInitialized()) return const file = await decryptConfigFile(encryptedContent) // 配置文件解密后包含 appKey 和 platformUrl(字段名 serverUrl 或 baseUrl) const platformUrl = file.serverUrl ?? file.baseUrl ?? undefined await this.initialize({ appKey: file.appKey, platformUrl, debug: options?.debug }) }, /** * 等待初始化完成。在任意子 SDK 内部调用以确保 XuqmSDK 已就绪。 */ async awaitInitialization(): Promise { if (isInitialized()) return await ensureInitPromise() }, getUserId(): string | null { return getCommonUserId() }, /** * 用户认证核心枢纽。登录成功后调用一次,所有子 SDK(Push、IM、License 等)自动同步状态。 * 登出时传 null,触发所有子 SDK 登出。 */ setUserInfo(info: XuqmUserInfo | null): void { setCommonUserInfo(info) }, getUserInfo(): XuqmUserInfo | null { return getCommonUserInfo() }, } export async function awaitInitialization(): Promise { if (isInitialized()) return await ensureInitPromise() }