diff --git a/README.md b/README.md index 901852e..54332ee 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,6 @@ import { XuqmSDK } from '@xuqm/rn-sdk' await XuqmSDK.init({ appKey: 'ak_your_app_key', - appSecret: 'as_your_app_secret', - apiBaseUrl: 'https://api.xuqm.com', - imBaseUrl: 'wss://im.xuqm.com', debug: __DEV__, }) ``` diff --git a/packages/common/src/config.ts b/packages/common/src/config.ts index 4bba66f..b24ec34 100644 --- a/packages/common/src/config.ts +++ b/packages/common/src/config.ts @@ -1,16 +1,13 @@ export interface XuqmInitOptions { - appId: string - serverUrl: string // e.g. "http://192.168.116.9:8081" — SDK fetches config from here - appKey?: string + appKey: string debug?: boolean } export interface XuqmConfig { appId: string appKey: string - serverUrl: string - apiBaseUrl: string // im-service base URL - imWsUrl: string // fetched from remote config + apiUrl: string + imWsUrl: string fileServiceUrl: string // fetched from remote config debug: boolean } @@ -20,13 +17,12 @@ let _userId: string | null = null export function initConfigFromRemote( options: XuqmInitOptions, - remote: { imWsUrl: string; fileServiceUrl: string; apiBaseUrl: string }, + remote: { imWsUrl: string; fileServiceUrl: string; apiUrl: string }, ): void { _config = { - appId: options.appId, - appKey: options.appKey ?? options.appId, - serverUrl: options.serverUrl, - apiBaseUrl: remote.apiBaseUrl, + appId: options.appKey, + appKey: options.appKey, + apiUrl: remote.apiUrl, imWsUrl: remote.imWsUrl, fileServiceUrl: remote.fileServiceUrl, debug: options.debug ?? false, diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index 0d02e60..ded7e83 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -1,12 +1,2 @@ -/** - * @deprecated These hardcoded URLs are no longer used by the SDK. - * The SDK now fetches configuration from the tenant platform via - * XuqmSDK.initialize(). These constants are kept only as fallback - * references and for backward compatibility. - */ -export const API_BASE_URL = 'http://192.168.116.9:8081' - -/** - * @deprecated See API_BASE_URL. - */ -export const IM_WS_URL = 'ws://192.168.116.9:8082/ws/im' +export const DEFAULT_TENANT_PLATFORM_URL = 'https://dev.xuqinmin.com' +export const DEFAULT_IM_WS_URL = 'wss://dev.xuqinmin.com/ws/im' diff --git a/packages/common/src/http.ts b/packages/common/src/http.ts index 8b1982c..64b906b 100644 --- a/packages/common/src/http.ts +++ b/packages/common/src/http.ts @@ -26,7 +26,7 @@ export async function apiRequest( ): Promise { const config = getConfig() - let url = config.apiBaseUrl + path + let url = config.apiUrl + path if (options.params) { const qs = new URLSearchParams(options.params).toString() url += (url.includes('?') ? '&' : '?') + qs diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index cad6108..71f71e6 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -2,7 +2,7 @@ export { XuqmSDK } from './sdk' export type { XuqmInitOptions, XuqmConfig } from './config' export { getConfig, isInitialized, setUserId, getUserId } from './config' export { apiRequest, _getToken, _saveToken, _clearToken } from './http' -export { API_BASE_URL, IM_WS_URL } from './constants' +export { DEFAULT_TENANT_PLATFORM_URL, DEFAULT_IM_WS_URL } from './constants' export { getDeviceId, getDeviceInfo, detectPushVendor } from './device' export type { DeviceInfo, PushVendor } from './device' export { ScaledImage } from './components/ScaledImage' diff --git a/packages/common/src/sdk.ts b/packages/common/src/sdk.ts index 192bf6b..e0095c4 100644 --- a/packages/common/src/sdk.ts +++ b/packages/common/src/sdk.ts @@ -1,18 +1,14 @@ import { initConfigFromRemote, isInitialized, type XuqmInitOptions, setUserId as setCommonUserId, getUserId as getCommonUserId } from './config' +import { DEFAULT_IM_WS_URL, DEFAULT_TENANT_PLATFORM_URL } from './constants' export const XuqmSDK = { /** - * Async initialize — fetches SDK config from the tenant platform. - * Recommended for production use. - * - * @param options.appId - Your application ID (from the tenant platform) - * @param options.serverUrl - Base URL of the tenant platform, e.g. "http://192.168.116.9:8081" - * @param options.appKey - Optional; defaults to appId + * @param options.appKey - Your application key (from the tenant platform) * @param options.debug - Enable verbose logging */ async initialize(options: XuqmInitOptions): Promise { if (isInitialized()) return - const configUrl = `${options.serverUrl}/api/sdk/config?appId=${options.appId}` + const configUrl = `${DEFAULT_TENANT_PLATFORM_URL}/api/sdk/config?appId=${options.appKey}` try { const res = await fetch(configUrl) const json = await res.json() @@ -20,34 +16,29 @@ export const XuqmSDK = { initConfigFromRemote(options, { imWsUrl: remote.imWsUrl, fileServiceUrl: remote.fileServiceUrl, - apiBaseUrl: remote.imApiUrl ?? options.serverUrl, + apiUrl: remote.imApiUrl ?? DEFAULT_TENANT_PLATFORM_URL, }) } catch (e) { - // Fallback: construct URLs from serverUrl + // Fallback: construct URLs from the built-in platform endpoint initConfigFromRemote(options, { - imWsUrl: options.serverUrl.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws/im', - fileServiceUrl: options.serverUrl, - apiBaseUrl: options.serverUrl, + imWsUrl: DEFAULT_IM_WS_URL, + fileServiceUrl: DEFAULT_TENANT_PLATFORM_URL, + apiUrl: DEFAULT_TENANT_PLATFORM_URL, }) if (options.debug) console.warn('[XuqmSDK] Config fetch failed, using fallback URLs', e) } }, /** - * Sync initialize — uses fallback URL construction from serverUrl. - * Kept for backward compatibility; prefer initialize() for production use. - * - * @param options.appId - Your application ID (from the tenant platform) - * @param options.serverUrl - Base URL of the tenant platform, e.g. "http://192.168.116.9:8081" - * @param options.appKey - Optional; defaults to appId + * @param options.appKey - Your application key (from the tenant platform) * @param options.debug - Enable verbose logging */ init(options: XuqmInitOptions): void { if (isInitialized()) return initConfigFromRemote(options, { - imWsUrl: options.serverUrl.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws/im', - fileServiceUrl: options.serverUrl, - apiBaseUrl: options.serverUrl, + imWsUrl: DEFAULT_IM_WS_URL, + fileServiceUrl: DEFAULT_TENANT_PLATFORM_URL, + apiUrl: DEFAULT_TENANT_PLATFORM_URL, }) }, diff --git a/packages/update/scripts/xuqm_release.mjs b/packages/update/scripts/xuqm_release.mjs index c31fe04..ad1e40e 100644 --- a/packages/update/scripts/xuqm_release.mjs +++ b/packages/update/scripts/xuqm_release.mjs @@ -9,13 +9,14 @@ * Config: xuqm.config.json in the project root * { * "serverUrl": "https://update.dev.xuqinmin.com", - * "appId": "your-app-id", + * "appKey": "your-app-key", * "apiToken": "your-api-token", * "rn": { * "modules": [ - * { "moduleId": "main", "entryFile": "index.js", "platforms": ["android", "ios"] } + * { "moduleId": "main", "entryFile": "index.js", "packageName": "com.example.app", "platforms": ["android", "ios"] } * ], - * "bundleOutputDir": "/tmp/xuqm_rn_bundle" + * "bundleOutputDir": "/tmp/xuqm_rn_bundle", + * "publishImmediately": false * } * } * @@ -36,13 +37,14 @@ if (!existsSync(CONFIG_FILE)) { process.exit(1) } const cfg = JSON.parse(readFileSync(CONFIG_FILE, 'utf8')) -const { serverUrl, appId, apiToken, rn = {} } = cfg -if (!serverUrl || !appId || !apiToken) { - console.error('[xuqm] serverUrl / appId / apiToken are required in config') +const { serverUrl, appKey, apiToken, rn = {} } = cfg +if (!serverUrl || !appKey || !apiToken) { + console.error('[xuqm] serverUrl / appKey / apiToken are required in config') process.exit(1) } const modules = rn.modules ?? [{ moduleId: 'main', entryFile: 'index.js', platforms: ['android', 'ios'] }] const bundleOutputDir = rn.bundleOutputDir ?? '/tmp/xuqm_rn_bundle' +const publishImmediately = rn.publishImmediately === true || rn.publishImmediately === 'true' // ── Helpers ───────────────────────────────────────────────────────────────── @@ -59,15 +61,16 @@ async function apiFetch(path, opts = {}) { } // Multipart upload using FormData (Node 18+ has built-in FormData) -async function uploadBundle(moduleId, platform, bundleFile, version, minVersion, note) { +async function uploadBundle(moduleId, platform, bundleFile, version, minVersion, note, packageName) { const form = new FormData() const blob = new Blob([readFileSync(bundleFile)], { type: 'application/octet-stream' }) - form.append('appId', appId) + form.append('appId', appKey) form.append('moduleId', moduleId) form.append('platform', platform.toUpperCase()) form.append('version', version) form.append('minCommonVersion', minVersion) form.append('note', note) + if (packageName) form.append('packageName', packageName) form.append('bundle', blob, path.basename(bundleFile)) const res = await fetch(`${serverUrl}/api/v1/rn/upload`, { @@ -92,7 +95,7 @@ async function main() { // ── 2. Server latest ───────────────────────────────────────────────────── let serverVersion = 'none' try { - const resp = await apiFetch(`/api/v1/rn/list?appId=${appId}`) + const resp = await apiFetch(`/api/v1/rn/list?appId=${appKey}`) const published = (resp.data ?? []).filter(x => x.publishStatus === 'PUBLISHED') serverVersion = published[0]?.version ?? 'none' } catch { /* no bundles yet */ } @@ -113,6 +116,7 @@ async function main() { console.log('\n\x1b[36m--- Summary ---\x1b[0m') console.log(` Version: ${localVersion}`) console.log(` Modules: ${modules.map(m => m.moduleId).join(', ')}`) + const publishNow = publishImmediately || await confirm('Publish uploaded bundles immediately?') const ok = await confirm('Proceed?') if (!ok) { console.log('Aborted.'); rl.close(); return } @@ -139,10 +143,21 @@ async function main() { // Upload console.log('\x1b[36mUploading...\x1b[0m') - const resp = await uploadBundle(mod.moduleId, platform, bundleFile, localVersion, minVersion, note) + const resp = await uploadBundle(mod.moduleId, platform, bundleFile, localVersion, minVersion, note, mod.packageName) const bundleId = resp.data?.id console.log(`\x1b[32m✓ ${mod.moduleId}/${platform} uploaded, ID: ${bundleId}\x1b[0m`) - console.log(` Publish: POST ${serverUrl}/api/v1/rn/${bundleId}/publish`) + if (publishNow) { + const publishResp = await fetch(`${serverUrl}/api/v1/rn/${bundleId}/publish`, { + method: 'POST', + headers: apiHeaders(), + }) + if (!publishResp.ok) { + throw new Error(`Publish failed: ${publishResp.status} ${await publishResp.text()}`) + } + console.log(` Published immediately: POST ${serverUrl}/api/v1/rn/${bundleId}/publish`) + } else { + console.log(` Publish: POST ${serverUrl}/api/v1/rn/${bundleId}/publish`) + } } } diff --git a/src/core/config.ts b/src/core/config.ts index ec229f6..d1c6521 100644 --- a/src/core/config.ts +++ b/src/core/config.ts @@ -1,9 +1,6 @@ export interface XuqmSDKConfig { - appId: string appKey: string appSecret: string - apiBaseUrl: string - imWsUrl: string debug?: boolean } diff --git a/src/core/http.ts b/src/core/http.ts index f499231..b019708 100644 --- a/src/core/http.ts +++ b/src/core/http.ts @@ -2,6 +2,7 @@ import AsyncStorage from '@react-native-async-storage/async-storage' import { getConfig } from './config' const TOKEN_KEY = '@xuqm_sdk_token' +const DEFAULT_API_URL = 'https://dev.xuqinmin.com' export async function getToken(): Promise { return AsyncStorage.getItem(TOKEN_KEY) @@ -22,7 +23,7 @@ export async function apiRequest( const config = getConfig() const token = await getToken() - let url = config.apiBaseUrl.replace(/\/$/, '') + path + let url = DEFAULT_API_URL.replace(/\/$/, '') + path if (options.params) { const qs = new URLSearchParams(options.params).toString() url += (url.includes('?') ? '&' : '?') + qs diff --git a/src/im/imSDK.ts b/src/im/imSDK.ts index 03faed7..7a546c8 100644 --- a/src/im/imSDK.ts +++ b/src/im/imSDK.ts @@ -4,6 +4,7 @@ import { ImClient } from './imClient' import type { ChatType, ImEventListener, ImGroup, ImMessage, MsgType } from './types' let client: ImClient | null = null +const DEFAULT_IM_WS_URL = 'wss://dev.xuqinmin.com/ws/im' export const ImSDK = { async login(userId: string, nickname?: string, avatar?: string): Promise { @@ -11,14 +12,14 @@ export const ImSDK = { const res = await apiRequest<{ token: string }>('/api/im/auth/login', { method: 'POST', params: { - appId: config.appId, + appId: config.appKey, userId, ...(nickname ? { nickname } : {}), ...(avatar ? { avatar } : {}), }, }) await saveToken(res.token) - client = new ImClient(config.imWsUrl, res.token, config.appId) + client = new ImClient(DEFAULT_IM_WS_URL, res.token, config.appKey) client.connect() }, @@ -26,7 +27,7 @@ export const ImSDK = { const config = getConfig() const token = await getToken() if (!token) throw new Error('ImSDK: token not found') - client = new ImClient(config.imWsUrl, token, config.appId) + client = new ImClient(DEFAULT_IM_WS_URL, token, config.appKey) client.connect() }, @@ -34,7 +35,7 @@ export const ImSDK = { const config = getConfig() const res = await apiRequest<{ content?: ImMessage[] } | ImMessage[]>(`/api/im/messages/history/${encodeURIComponent(toId)}`, { params: { - appId: config.appId, + appId: config.appKey, page: String(page), size: String(size), }, @@ -52,7 +53,7 @@ export const ImSDK = { const config = getConfig() return apiRequest('/api/im/messages/send', { method: 'POST', - params: { appId: config.appId }, + params: { appId: config.appKey }, body: { toId, chatType, @@ -67,7 +68,7 @@ export const ImSDK = { const config = getConfig() return apiRequest(`/api/im/messages/${encodeURIComponent(messageId)}/revoke`, { method: 'POST', - params: { appId: config.appId }, + params: { appId: config.appKey }, }) }, @@ -75,7 +76,7 @@ export const ImSDK = { const config = getConfig() return apiRequest(`/api/im/messages/${encodeURIComponent(messageId)}`, { method: 'PUT', - params: { appId: config.appId }, + params: { appId: config.appKey }, body: { content }, }) }, @@ -105,7 +106,7 @@ export const ImSDK = { const config = getConfig() return apiRequest('/api/im/groups', { method: 'POST', - params: { appId: config.appId }, + params: { appId: config.appKey }, body: { name, memberIds }, }) }, @@ -113,7 +114,7 @@ export const ImSDK = { async listGroups(): Promise { const config = getConfig() const res = await apiRequest('/api/im/groups', { - params: { appId: config.appId }, + params: { appId: config.appKey }, }) return Array.isArray(res) ? res : (res.content ?? []) }, @@ -124,7 +125,7 @@ export const ImSDK = { `/api/im/messages/history/${encodeURIComponent(groupId)}`, { params: { - appId: config.appId, + appId: config.appKey, page: String(page), size: String(size), }, diff --git a/src/push/pushSDK.ts b/src/push/pushSDK.ts index 21c1d7a..ef2b0dd 100644 --- a/src/push/pushSDK.ts +++ b/src/push/pushSDK.ts @@ -10,7 +10,7 @@ export const PushSDK = { await apiRequest('/api/push/register', { method: 'POST', params: { - appId: config.appId, + appId: config.appKey, userId, vendor, token, @@ -22,7 +22,7 @@ export const PushSDK = { const config = getConfig() await apiRequest('/api/push/unregister', { method: 'DELETE', - params: { appId: config.appId, userId }, + params: { appId: config.appKey, userId }, }) }, } diff --git a/src/update/updateSDK.ts b/src/update/updateSDK.ts index 8fa54a9..9fb7716 100644 --- a/src/update/updateSDK.ts +++ b/src/update/updateSDK.ts @@ -21,6 +21,8 @@ export interface RnUpdateInfo { md5: string minCommonVersion: string note: string + packageName?: string + packageMatched?: boolean } export interface CachedRnBundle { @@ -62,7 +64,7 @@ export const UpdateSDK = { const config = getConfig() const result = await apiRequest('/api/v1/updates/app/check', { params: { - appId: config.appId, + appId: config.appKey, platform: Platform.OS === 'android' ? 'ANDROID' : 'IOS', currentVersionCode: String(currentVersionCode), }, @@ -78,16 +80,23 @@ export const UpdateSDK = { if (url) await Linking.openURL(url) }, - async checkRnUpdate(moduleId: string, currentVersion: string): Promise { + async checkRnUpdate(moduleId: string, currentVersion: string, packageName?: string): Promise { const config = getConfig() const result = await apiRequest('/api/v1/rn/update/check', { params: { - appId: config.appId, + appId: config.appKey, moduleId, platform: Platform.OS === 'android' ? 'ANDROID' : 'IOS', currentVersion, + ...(packageName ? { packageName } : {}), }, }) + if (packageName && result.packageMatched === false) { + return { + ...result, + needsUpdate: false, + } + } return { ...result, downloadUrl: normalizeDownloadUrl(result.downloadUrl) ?? result.downloadUrl,