diff --git a/package.json b/package.json index 7f2c5f0..1bc0405 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,11 @@ "types": "./dist/index.d.ts", "import": "./dist/index.es.js", "require": "./dist/index.cjs.js" + }, + "./private": { + "types": "./dist/private/index.d.ts", + "import": "./dist/private/index.es.js", + "require": "./dist/private/index.cjs.js" } }, "files": [ diff --git a/package.private.json b/package.private.json new file mode 100644 index 0000000..114f5ab --- /dev/null +++ b/package.private.json @@ -0,0 +1,25 @@ +{ + "name": "@xuqm-private/vue3-sdk", + "version": "0.2.3", + "description": "XuqmGroup Vue3 Private SDK — for private deployment environments", + "private": false, + "publishConfig": { + "registry": "https://nexus.xuqinmin.com/repository/npm-hosted/" + }, + "main": "dist/private/index.cjs.js", + "module": "dist/private/index.es.js", + "types": "dist/private/index.d.ts", + "exports": { + ".": { + "types": "./dist/private/index.d.ts", + "import": "./dist/private/index.es.js", + "require": "./dist/private/index.cjs.js" + } + }, + "files": [ + "dist/private" + ], + "peerDependencies": { + "vue": "^3.5.0" + } +} diff --git a/src/private/index.ts b/src/private/index.ts new file mode 100644 index 0000000..a80e0f2 --- /dev/null +++ b/src/private/index.ts @@ -0,0 +1,15 @@ +export { + initFromConfig, + initFromFile, + reloadConfig, + requireFeature, + getPrivateConfig, + isFeatureEnabled, + setToken, + setUserId, + getToken, + getUserId, +} from './sdk' + +export type { PrivateSdkConfig, PrivateSdkFeatures, PrivateSdkState, PrivateErrorCode } from './types' +export { PrivateSdkError } from './types' diff --git a/src/private/sdk.ts b/src/private/sdk.ts new file mode 100644 index 0000000..85cb819 --- /dev/null +++ b/src/private/sdk.ts @@ -0,0 +1,138 @@ +import type { PrivateSdkConfig, PrivateSdkState } from './types' +import { PrivateSdkError } from './types' +import { configureHttp } from '../core/http' + +const SUPPORTED_SCHEMA_VERSIONS = [1] + +const state: PrivateSdkState = { + config: null, + token: null, + userId: null, + initialized: false, +} + +function validateConfig(cfg: PrivateSdkConfig): void { + if (!SUPPORTED_SCHEMA_VERSIONS.includes(cfg.schemaVersion)) { + throw new PrivateSdkError( + 'XUQM_PRIVATE_1003', + `Unsupported schemaVersion: ${cfg.schemaVersion}. Supported: ${SUPPORTED_SCHEMA_VERSIONS.join(', ')}`, + ) + } + if (cfg.deployment !== 'PRIVATE') { + throw new PrivateSdkError('XUQM_PRIVATE_1002', 'deployment must be "PRIVATE"') + } + if (!cfg.appKey) { + throw new PrivateSdkError('XUQM_PRIVATE_1002', 'appKey is required') + } + if (!cfg.controlBaseUrl) { + throw new PrivateSdkError('XUQM_PRIVATE_1002', 'controlBaseUrl is required') + } + validateUrl('controlBaseUrl', cfg.controlBaseUrl, false) + if (cfg.features.im) { + if (!cfg.imApiBaseUrl) throw new PrivateSdkError('XUQM_PRIVATE_1002', 'imApiBaseUrl is required when im is enabled') + if (!cfg.imWsUrl) throw new PrivateSdkError('XUQM_PRIVATE_1002', 'imWsUrl is required when im is enabled') + validateUrl('imApiBaseUrl', cfg.imApiBaseUrl, false) + validateWsUrl('imWsUrl', cfg.imWsUrl) + } + if (cfg.features.push && !cfg.pushBaseUrl) { + throw new PrivateSdkError('XUQM_PRIVATE_1002', 'pushBaseUrl is required when push is enabled') + } + if (cfg.features.update && !cfg.updateBaseUrl) { + throw new PrivateSdkError('XUQM_PRIVATE_1002', 'updateBaseUrl is required when update is enabled') + } + if (cfg.features.license && !cfg.licenseBaseUrl) { + throw new PrivateSdkError('XUQM_PRIVATE_1002', 'licenseBaseUrl is required when license is enabled') + } +} + +function validateUrl(field: string, url: string, optional: boolean): void { + if (!url) { + if (!optional) throw new PrivateSdkError('XUQM_PRIVATE_1004', `${field} is required`) + return + } + if (!url.startsWith('http://') && !url.startsWith('https://')) { + throw new PrivateSdkError('XUQM_PRIVATE_1004', `${field} must start with http:// or https://`) + } +} + +function validateWsUrl(field: string, url: string): void { + if (!url.startsWith('ws://') && !url.startsWith('wss://')) { + throw new PrivateSdkError('XUQM_PRIVATE_1004', `${field} must start with ws:// or wss://`) + } +} + +function resolveActiveConfig(cfg: PrivateSdkConfig): PrivateSdkConfig { + if (!cfg.activeProfile || !cfg.profiles) return cfg + const profile = cfg.profiles[cfg.activeProfile] + if (!profile) return cfg + return { ...cfg, ...profile, activeProfile: cfg.activeProfile, profiles: cfg.profiles } +} + +function applyConfig(cfg: PrivateSdkConfig): void { + const resolved = resolveActiveConfig(cfg) + validateConfig(resolved) + state.config = resolved + state.initialized = true + // wire up the shared http client with the control base URL (no default fallback) + configureHttp(normalizeUrl(resolved.controlBaseUrl), () => state.token) +} + +function normalizeUrl(url: string): string { + return url.endsWith('/') ? url.slice(0, -1) : url +} + +export function initFromConfig(cfg: PrivateSdkConfig): void { + applyConfig(cfg) +} + +export async function initFromFile(url: string): Promise { + const res = await fetch(url) + if (!res.ok) throw new PrivateSdkError('XUQM_PRIVATE_1002', `Failed to load config from ${url}: ${res.status}`) + const cfg = (await res.json()) as PrivateSdkConfig + applyConfig(cfg) +} + +export async function reloadConfig(url: string): Promise { + await initFromFile(url) +} + +export function requireFeature( + feature: keyof PrivateSdkConfig['features'], +): void { + if (!state.config) { + throw new PrivateSdkError('XUQM_PRIVATE_1001', 'Private SDK not initialized. Call initFromConfig() or initFromFile() first.') + } + if (!state.config.features[feature]) { + throw new PrivateSdkError( + 'XUQM_PRIVATE_2001', + `Feature "${feature}" is not enabled in this deployment. Contact your administrator to enable it.`, + ) + } +} + +export function getPrivateConfig(): PrivateSdkConfig { + if (!state.config) { + throw new PrivateSdkError('XUQM_PRIVATE_1001', 'Private SDK not initialized.') + } + return state.config +} + +export function isFeatureEnabled(feature: keyof PrivateSdkConfig['features']): boolean { + return !!state.config?.features[feature] +} + +export function setToken(token: string | null): void { + state.token = token +} + +export function setUserId(userId: string | null): void { + state.userId = userId +} + +export function getToken(): string | null { + return state.token +} + +export function getUserId(): string | null { + return state.userId +} diff --git a/src/private/types.ts b/src/private/types.ts new file mode 100644 index 0000000..1c3c1f9 --- /dev/null +++ b/src/private/types.ts @@ -0,0 +1,53 @@ +export interface PrivateSdkFeatures { + file?: boolean + im?: boolean + push?: boolean + update?: boolean + license?: boolean +} + +export interface PrivateSdkConfig { + schemaVersion: number + configVersion?: string + deployment: 'PRIVATE' + appKey: string + controlBaseUrl: string + imApiBaseUrl?: string + imWsUrl?: string + pushBaseUrl?: string + updateBaseUrl?: string + fileBaseUrl?: string + licenseBaseUrl?: string + docsBaseUrl?: string + features: PrivateSdkFeatures + connectTimeoutMs?: number + readTimeoutMs?: number + logLevel?: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' + activeProfile?: string + profiles?: Record> +} + +export interface PrivateSdkState { + config: PrivateSdkConfig | null + token: string | null + userId: string | null + initialized: boolean +} + +export type PrivateErrorCode = + | 'XUQM_PRIVATE_1001' // config not initialized / JSON not found + | 'XUQM_PRIVATE_1002' // invalid config: missing required field or parse error + | 'XUQM_PRIVATE_1003' // schema version incompatible + | 'XUQM_PRIVATE_1004' // invalid URL format + | 'XUQM_PRIVATE_2001' // feature not enabled in deployment + | 'XUQM_PRIVATE_2004' // module not initialized + +export class PrivateSdkError extends Error { + constructor( + public readonly code: PrivateErrorCode, + message: string, + ) { + super(`[${code}] ${message}`) + this.name = 'PrivateSdkError' + } +} diff --git a/vite.config.ts b/vite.config.ts index bc859d9..7994526 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -10,10 +10,13 @@ export default defineConfig({ ], build: { lib: { - entry: resolve(__dirname, 'src/index.ts'), + entry: { + index: resolve(__dirname, 'src/index.ts'), + 'private/index': resolve(__dirname, 'src/private/index.ts'), + }, name: 'XuqmVue3SDK', formats: ['es', 'cjs'], - fileName: (format) => `index.${format}.js`, + fileName: (format, entryName) => `${entryName}.${format}.js`, }, rollupOptions: { external: ['vue'],