chore: initial commit
这个提交包含在:
当前提交
90bd57a69c
10
.gitignore
vendored
普通文件
10
.gitignore
vendored
普通文件
@ -0,0 +1,10 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.DS_Store
|
||||||
|
*.class
|
||||||
|
target/
|
||||||
|
build/
|
||||||
|
.gradle/
|
||||||
|
*.iml
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
39
build-profile.json5
普通文件
39
build-profile.json5
普通文件
@ -0,0 +1,39 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"signingConfigs": [],
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"signingConfig": "default",
|
||||||
|
"compatibleSdkVersion": "5.0.0(12)",
|
||||||
|
"runtimeOS": "HarmonyOS",
|
||||||
|
"buildOption": {
|
||||||
|
"strictMode": {
|
||||||
|
"caseSensitiveCheck": true,
|
||||||
|
"useNormalizedOHMUrl": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildModeSet": [
|
||||||
|
{ "name": "debug" },
|
||||||
|
{ "name": "release" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"name": "xuqm-sdk",
|
||||||
|
"srcPath": "./xuqm-sdk",
|
||||||
|
"targets": [
|
||||||
|
{ "name": "default", "applyToProducts": ["default"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "entry",
|
||||||
|
"srcPath": "./entry",
|
||||||
|
"targets": [
|
||||||
|
{ "name": "default", "applyToProducts": ["default"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
7
entry/build-profile.json5
普通文件
7
entry/build-profile.json5
普通文件
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"apiType": "stageMode",
|
||||||
|
"buildOption": {},
|
||||||
|
"targets": [
|
||||||
|
{ "name": "default", "runtimeOS": "HarmonyOS" }
|
||||||
|
]
|
||||||
|
}
|
||||||
6
entry/hvigorfile.ts
普通文件
6
entry/hvigorfile.ts
普通文件
@ -0,0 +1,6 @@
|
|||||||
|
import { hapTasks } from '@ohos/hvigor-ohos-plugin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
system: hapTasks,
|
||||||
|
plugins: []
|
||||||
|
}
|
||||||
11
entry/oh-package.json5
普通文件
11
entry/oh-package.json5
普通文件
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "entry",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "SDK sample app",
|
||||||
|
"main": "",
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@xuqm/harmony-sdk": "file:../xuqm-sdk"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import UIAbility from '@ohos.app.ability.UIAbility'
|
||||||
|
import hilog from '@ohos.hilog'
|
||||||
|
import window from '@ohos.window'
|
||||||
|
import { XuqmSDK } from '@xuqm/harmony-sdk'
|
||||||
|
|
||||||
|
export default class EntryAbility extends UIAbility {
|
||||||
|
async onCreate(): Promise<void> {
|
||||||
|
hilog.info(0x0000, 'EntryAbility', 'onCreate')
|
||||||
|
await XuqmSDK.init(this.context, {
|
||||||
|
appKey: 'YOUR_APP_KEY',
|
||||||
|
appSecret: 'YOUR_APP_SECRET',
|
||||||
|
apiBaseUrl: 'https://api.xuqm.com',
|
||||||
|
imBaseUrl: 'wss://im.xuqm.com',
|
||||||
|
debug: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowStageCreate(windowStage: window.WindowStage): void {
|
||||||
|
windowStage.loadContent('pages/Index', (err) => {
|
||||||
|
if (err.code) {
|
||||||
|
hilog.error(0x0000, 'EntryAbility', 'loadContent failed: %{public}s', JSON.stringify(err))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
import { XuqmSDK, ImMessage } from '@xuqm/harmony-sdk'
|
||||||
|
import promptAction from '@ohos.promptAction'
|
||||||
|
|
||||||
|
@Entry
|
||||||
|
@Component
|
||||||
|
struct Index {
|
||||||
|
@State messages: ImMessage[] = []
|
||||||
|
@State inputText: string = ''
|
||||||
|
@State connected: boolean = false
|
||||||
|
private toUserId: string = 'user_002'
|
||||||
|
|
||||||
|
aboutToAppear(): void {
|
||||||
|
const im = XuqmSDK.im
|
||||||
|
im.delegate = {
|
||||||
|
onConnected: () => {
|
||||||
|
this.connected = true
|
||||||
|
promptAction.showToast({ message: 'IM 已连接' })
|
||||||
|
},
|
||||||
|
onDisconnected: (code, reason) => {
|
||||||
|
this.connected = false
|
||||||
|
console.log(`IM disconnected: ${code} ${reason}`)
|
||||||
|
},
|
||||||
|
onMessage: (msg) => {
|
||||||
|
this.messages = [...this.messages, msg]
|
||||||
|
},
|
||||||
|
onError: (err) => {
|
||||||
|
promptAction.showToast({ message: 'IM 错误: ' + err })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
im.connect()
|
||||||
|
}
|
||||||
|
|
||||||
|
aboutToDisappear(): void {
|
||||||
|
XuqmSDK.im.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
build() {
|
||||||
|
Column({ space: 12 }) {
|
||||||
|
Row() {
|
||||||
|
Text('XuqmSDK 示例')
|
||||||
|
.fontSize(20)
|
||||||
|
.fontWeight(FontWeight.Bold)
|
||||||
|
Blank()
|
||||||
|
Text(this.connected ? '● 已连接' : '○ 未连接')
|
||||||
|
.fontSize(14)
|
||||||
|
.fontColor(this.connected ? Color.Green : Color.Gray)
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 16, right: 16, top: 16 })
|
||||||
|
|
||||||
|
List({ space: 8 }) {
|
||||||
|
ForEach(this.messages, (msg: ImMessage) => {
|
||||||
|
ListItem() {
|
||||||
|
Column({ space: 4 }) {
|
||||||
|
Text(msg.fromId).fontSize(12).fontColor(Color.Gray)
|
||||||
|
Text(msg.content).fontSize(15)
|
||||||
|
}
|
||||||
|
.alignItems(HorizontalAlign.Start)
|
||||||
|
.width('100%')
|
||||||
|
.padding(10)
|
||||||
|
.backgroundColor('#F5F5F5')
|
||||||
|
.borderRadius(8)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.layoutWeight(1)
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 16, right: 16 })
|
||||||
|
|
||||||
|
Row({ space: 8 }) {
|
||||||
|
TextInput({ placeholder: '输入消息...', text: this.inputText })
|
||||||
|
.layoutWeight(1)
|
||||||
|
.onChange((val) => { this.inputText = val })
|
||||||
|
|
||||||
|
Button('发送')
|
||||||
|
.onClick(() => {
|
||||||
|
if (!this.inputText.trim()) return
|
||||||
|
try {
|
||||||
|
XuqmSDK.im.send({
|
||||||
|
toId: this.toUserId,
|
||||||
|
chatType: 'SINGLE',
|
||||||
|
msgType: 'TEXT',
|
||||||
|
content: this.inputText.trim(),
|
||||||
|
})
|
||||||
|
this.inputText = ''
|
||||||
|
} catch (e) {
|
||||||
|
promptAction.showToast({ message: (e as Error).message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.padding({ left: 16, right: 16, bottom: 16 })
|
||||||
|
}
|
||||||
|
.width('100%')
|
||||||
|
.height('100%')
|
||||||
|
.backgroundColor(Color.White)
|
||||||
|
}
|
||||||
|
}
|
||||||
34
entry/src/main/module.json5
普通文件
34
entry/src/main/module.json5
普通文件
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"module": {
|
||||||
|
"name": "entry",
|
||||||
|
"type": "entry",
|
||||||
|
"description": "$string:module_desc",
|
||||||
|
"mainElement": "EntryAbility",
|
||||||
|
"deviceTypes": ["phone", "tablet"],
|
||||||
|
"deliveryWithInstall": true,
|
||||||
|
"installationFree": false,
|
||||||
|
"pages": "$profile:main_pages",
|
||||||
|
"abilities": [
|
||||||
|
{
|
||||||
|
"name": "EntryAbility",
|
||||||
|
"srcEntry": "./ets/entryability/EntryAbility.ets",
|
||||||
|
"description": "$string:EntryAbility_desc",
|
||||||
|
"icon": "$media:app_icon",
|
||||||
|
"label": "$string:EntryAbility_label",
|
||||||
|
"startWindowIcon": "$media:app_icon",
|
||||||
|
"startWindowBackground": "$color:start_window_background",
|
||||||
|
"exported": true,
|
||||||
|
"skills": [
|
||||||
|
{
|
||||||
|
"entities": ["entity.system.home"],
|
||||||
|
"actions": ["action.system.home"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"requestPermissions": [
|
||||||
|
{ "name": "ohos.permission.INTERNET" },
|
||||||
|
{ "name": "ohos.permission.GET_BUNDLE_INFO" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"color": [
|
||||||
|
{ "name": "start_window_background", "value": "#FFFFFF" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"string": [
|
||||||
|
{ "name": "module_desc", "value": "XuqmSDK Sample" },
|
||||||
|
{ "name": "EntryAbility_desc", "value": "XuqmSDK Sample App" },
|
||||||
|
{ "name": "EntryAbility_label", "value": "XuqmSDK" }
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"src": ["pages/Index"]
|
||||||
|
}
|
||||||
6
hvigorfile.ts
普通文件
6
hvigorfile.ts
普通文件
@ -0,0 +1,6 @@
|
|||||||
|
import { appTasks } from '@ohos/hvigor-ohos-plugin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
system: appTasks,
|
||||||
|
plugins: []
|
||||||
|
}
|
||||||
7
oh-package.json5
普通文件
7
oh-package.json5
普通文件
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "xuqm-harmony-sdk-workspace",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "XuqmGroup HarmonyOS SDK workspace",
|
||||||
|
"author": "xuqm",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
19
xuqm-sdk/Index.ets
普通文件
19
xuqm-sdk/Index.ets
普通文件
@ -0,0 +1,19 @@
|
|||||||
|
export { XuqmSDK } from './src/main/ets/XuqmSDK'
|
||||||
|
export { ImClient } from './src/main/ets/im/ImClient'
|
||||||
|
export { PushSDK } from './src/main/ets/push/PushSDK'
|
||||||
|
export { UpdateSDK } from './src/main/ets/update/UpdateSDK'
|
||||||
|
export { SDKContext } from './src/main/ets/core/SDKContext'
|
||||||
|
export { HttpClient } from './src/main/ets/core/HttpClient'
|
||||||
|
export type {
|
||||||
|
SDKConfig,
|
||||||
|
ImMessage,
|
||||||
|
SendMessageParams,
|
||||||
|
AppVersionInfo,
|
||||||
|
RnBundleInfo,
|
||||||
|
PushTokenInfo,
|
||||||
|
MsgType,
|
||||||
|
ChatType,
|
||||||
|
ApiResponse,
|
||||||
|
} from './src/main/ets/core/Types'
|
||||||
|
export type { ImEventDelegate, RevokeData } from './src/main/ets/im/ImClient'
|
||||||
|
export type { AppUpdateResult, RnUpdateResult } from './src/main/ets/update/UpdateSDK'
|
||||||
7
xuqm-sdk/build-profile.json5
普通文件
7
xuqm-sdk/build-profile.json5
普通文件
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"apiType": "stageMode",
|
||||||
|
"buildOption": {},
|
||||||
|
"targets": [
|
||||||
|
{ "name": "default" }
|
||||||
|
]
|
||||||
|
}
|
||||||
6
xuqm-sdk/hvigorfile.ts
普通文件
6
xuqm-sdk/hvigorfile.ts
普通文件
@ -0,0 +1,6 @@
|
|||||||
|
import { harTasks } from '@ohos/hvigor-ohos-plugin'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
system: harTasks,
|
||||||
|
plugins: []
|
||||||
|
}
|
||||||
12
xuqm-sdk/oh-package.json5
普通文件
12
xuqm-sdk/oh-package.json5
普通文件
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "@xuqm/harmony-sdk",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "XuqmGroup HarmonyOS SDK — IM, Push, Version Management",
|
||||||
|
"main": "Index.ets",
|
||||||
|
"author": "xuqm",
|
||||||
|
"license": "MIT",
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://ohpm.openharmony.cn/ohpm/"
|
||||||
|
},
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import common from '@ohos.app.ability.common'
|
||||||
|
import type { SDKConfig } from './core/Types'
|
||||||
|
import { SDKContext } from './core/SDKContext'
|
||||||
|
import { ImClient } from './im/ImClient'
|
||||||
|
import { PushSDK } from './push/PushSDK'
|
||||||
|
import { UpdateSDK } from './update/UpdateSDK'
|
||||||
|
|
||||||
|
export class XuqmSDK {
|
||||||
|
private static _imClient: ImClient | null = null
|
||||||
|
|
||||||
|
static async init(context: common.UIAbilityContext, config: SDKConfig): Promise<void> {
|
||||||
|
SDKContext.init(config)
|
||||||
|
await SDKContext.initPreferences(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setToken(token: string | null): Promise<void> {
|
||||||
|
await SDKContext.setToken(token)
|
||||||
|
}
|
||||||
|
|
||||||
|
static getToken(): string | null {
|
||||||
|
return SDKContext.getToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
static get im(): ImClient {
|
||||||
|
if (!XuqmSDK._imClient) {
|
||||||
|
XuqmSDK._imClient = new ImClient()
|
||||||
|
}
|
||||||
|
return XuqmSDK._imClient
|
||||||
|
}
|
||||||
|
|
||||||
|
static get push(): typeof PushSDK {
|
||||||
|
return PushSDK
|
||||||
|
}
|
||||||
|
|
||||||
|
static get update(): typeof UpdateSDK {
|
||||||
|
return UpdateSDK
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
import http from '@ohos.net.http'
|
||||||
|
import type { ApiResponse } from './Types'
|
||||||
|
import { SDKContext } from './SDKContext'
|
||||||
|
|
||||||
|
export class HttpClient {
|
||||||
|
static async request<T>(method: http.RequestMethod, path: string, body?: object): Promise<T> {
|
||||||
|
const config = SDKContext.getConfig()
|
||||||
|
const token = SDKContext.getToken()
|
||||||
|
const url = config.apiBaseUrl.replace(/\/$/, '') + path
|
||||||
|
|
||||||
|
const client = http.createHttp()
|
||||||
|
try {
|
||||||
|
const options: http.HttpRequestOptions = {
|
||||||
|
method,
|
||||||
|
header: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
||||||
|
},
|
||||||
|
extraData: body ? JSON.stringify(body) : undefined,
|
||||||
|
connectTimeout: 15000,
|
||||||
|
readTimeout: 15000,
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await client.request(url, options)
|
||||||
|
const json: ApiResponse<T> = JSON.parse(res.result as string) as ApiResponse<T>
|
||||||
|
if (json.code !== 200) throw new Error(json.message)
|
||||||
|
return json.data
|
||||||
|
} finally {
|
||||||
|
client.destroy()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get<T>(path: string): Promise<T> {
|
||||||
|
return HttpClient.request<T>(http.RequestMethod.GET, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
static post<T>(path: string, body?: object): Promise<T> {
|
||||||
|
return HttpClient.request<T>(http.RequestMethod.POST, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
static put<T>(path: string, body?: object): Promise<T> {
|
||||||
|
return HttpClient.request<T>(http.RequestMethod.PUT, path, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
static delete<T>(path: string): Promise<T> {
|
||||||
|
return HttpClient.request<T>(http.RequestMethod.DELETE, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import preferences from '@ohos.data.preferences'
|
||||||
|
import type { SDKConfig } from './Types'
|
||||||
|
|
||||||
|
const TOKEN_KEY = 'xuqm_token'
|
||||||
|
const PREF_NAME = 'xuqm_sdk_prefs'
|
||||||
|
|
||||||
|
export class SDKContext {
|
||||||
|
private static _config: SDKConfig | null = null
|
||||||
|
private static _token: string | null = null
|
||||||
|
private static _pref: preferences.Preferences | null = null
|
||||||
|
|
||||||
|
static init(config: SDKConfig): void {
|
||||||
|
SDKContext._config = config
|
||||||
|
if (config.debug) {
|
||||||
|
console.log('[XuqmSDK] init appKey=' + config.appKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getConfig(): SDKConfig {
|
||||||
|
if (!SDKContext._config) {
|
||||||
|
throw new Error('XuqmSDK not initialized. Call XuqmSDK.init() first.')
|
||||||
|
}
|
||||||
|
return SDKContext._config
|
||||||
|
}
|
||||||
|
|
||||||
|
static async initPreferences(context: Context): Promise<void> {
|
||||||
|
SDKContext._pref = await preferences.getPreferences(context, PREF_NAME)
|
||||||
|
const saved = await SDKContext._pref.get(TOKEN_KEY, '') as string
|
||||||
|
if (saved) SDKContext._token = saved
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setToken(token: string | null): Promise<void> {
|
||||||
|
SDKContext._token = token
|
||||||
|
if (SDKContext._pref) {
|
||||||
|
if (token) {
|
||||||
|
await SDKContext._pref.put(TOKEN_KEY, token)
|
||||||
|
} else {
|
||||||
|
await SDKContext._pref.delete(TOKEN_KEY)
|
||||||
|
}
|
||||||
|
await SDKContext._pref.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static getToken(): string | null {
|
||||||
|
return SDKContext._token
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
export interface SDKConfig {
|
||||||
|
appKey: string
|
||||||
|
appSecret: string
|
||||||
|
apiBaseUrl: string
|
||||||
|
imBaseUrl: string
|
||||||
|
debug: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
code: number
|
||||||
|
status: string
|
||||||
|
data: T
|
||||||
|
message: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MsgType =
|
||||||
|
| 'TEXT'
|
||||||
|
| 'IMAGE'
|
||||||
|
| 'VIDEO'
|
||||||
|
| 'AUDIO'
|
||||||
|
| 'FILE'
|
||||||
|
| 'CUSTOM'
|
||||||
|
| 'LOCATION'
|
||||||
|
| 'NOTIFY'
|
||||||
|
| 'RICH_TEXT'
|
||||||
|
| 'CALL_AUDIO'
|
||||||
|
| 'CALL_VIDEO'
|
||||||
|
| 'REVOKED'
|
||||||
|
| 'FORWARD'
|
||||||
|
|
||||||
|
export type ChatType = 'SINGLE' | 'GROUP'
|
||||||
|
|
||||||
|
export interface ImMessage {
|
||||||
|
id: string
|
||||||
|
fromId: string
|
||||||
|
toId: string
|
||||||
|
chatType: ChatType
|
||||||
|
msgType: MsgType
|
||||||
|
content: string
|
||||||
|
extra?: string
|
||||||
|
revoked: boolean
|
||||||
|
createdAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendMessageParams {
|
||||||
|
toId: string
|
||||||
|
chatType: ChatType
|
||||||
|
msgType: MsgType
|
||||||
|
content: string
|
||||||
|
extra?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppVersionInfo {
|
||||||
|
latestVersionCode: number
|
||||||
|
latestVersionName: string
|
||||||
|
downloadUrl: string
|
||||||
|
forceUpdate: boolean
|
||||||
|
releaseNotes: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RnBundleInfo {
|
||||||
|
bundleVersion: number
|
||||||
|
downloadUrl: string
|
||||||
|
md5: string
|
||||||
|
forceUpdate: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PushTokenInfo {
|
||||||
|
vendor: string
|
||||||
|
token: string
|
||||||
|
platform: string
|
||||||
|
}
|
||||||
@ -0,0 +1,102 @@
|
|||||||
|
import webSocket from '@ohos.net.webSocket'
|
||||||
|
import type { ImMessage, SendMessageParams } from '../core/Types'
|
||||||
|
import { SDKContext } from '../core/SDKContext'
|
||||||
|
|
||||||
|
export interface ImEventDelegate {
|
||||||
|
onConnected?(): void
|
||||||
|
onDisconnected?(code: number, reason: string): void
|
||||||
|
onMessage?(msg: ImMessage): void
|
||||||
|
onRevoke?(data: RevokeData): void
|
||||||
|
onError?(message: string): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RevokeData {
|
||||||
|
msgId: string
|
||||||
|
operatorId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_RECONNECT_DELAY = 30_000
|
||||||
|
|
||||||
|
export class ImClient {
|
||||||
|
private ws: webSocket.WebSocket | null = null
|
||||||
|
private reconnectDelay: number = 3_000
|
||||||
|
private reconnectTimer: number | null = null
|
||||||
|
private destroyed: boolean = false
|
||||||
|
delegate: ImEventDelegate | null = null
|
||||||
|
|
||||||
|
connect(): void {
|
||||||
|
if (this.destroyed) return
|
||||||
|
const config = SDKContext.getConfig()
|
||||||
|
const token = SDKContext.getToken() ?? ''
|
||||||
|
const url = `${config.imBaseUrl}/ws/im?token=${token}`
|
||||||
|
|
||||||
|
this.ws = webSocket.createWebSocket()
|
||||||
|
|
||||||
|
this.ws.on('open', (_err: Error, _value: Object) => {
|
||||||
|
this.reconnectDelay = 3_000
|
||||||
|
if (config.debug) console.log('[ImClient] connected')
|
||||||
|
this.delegate?.onConnected?.()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ws.on('message', (_err: Error, value: string | ArrayBuffer) => {
|
||||||
|
try {
|
||||||
|
const text = typeof value === 'string' ? value : new TextDecoder().decode(value)
|
||||||
|
const frame = JSON.parse(text) as { type: string; payload: object }
|
||||||
|
if (frame.type === 'MESSAGE') {
|
||||||
|
this.delegate?.onMessage?.(frame.payload as ImMessage)
|
||||||
|
} else if (frame.type === 'REVOKE') {
|
||||||
|
this.delegate?.onRevoke?.(frame.payload as RevokeData)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore malformed frames
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ws.on('close', (_err: Error, value: webSocket.CloseResult) => {
|
||||||
|
this.delegate?.onDisconnected?.(value.code, value.reason)
|
||||||
|
if (!this.destroyed) this.scheduleReconnect()
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ws.on('error', (_err: Error) => {
|
||||||
|
this.delegate?.onError?.(_err.message)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.ws.connect(url, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
send(params: SendMessageParams): void {
|
||||||
|
if (!this.ws) throw new Error('WebSocket not connected')
|
||||||
|
const frame = JSON.stringify({ destination: '/app/chat.send', payload: params })
|
||||||
|
this.ws.send(frame, (_err: Error) => {
|
||||||
|
if (_err) console.error('[ImClient] send error', _err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
revoke(msgId: string): void {
|
||||||
|
if (!this.ws) throw new Error('WebSocket not connected')
|
||||||
|
const frame = JSON.stringify({ destination: '/app/chat.revoke', payload: { msgId } })
|
||||||
|
this.ws.send(frame, (_err: Error) => {
|
||||||
|
if (_err) console.error('[ImClient] revoke error', _err.message)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnect(): void {
|
||||||
|
this.destroyed = true
|
||||||
|
if (this.reconnectTimer !== null) {
|
||||||
|
clearTimeout(this.reconnectTimer)
|
||||||
|
this.reconnectTimer = null
|
||||||
|
}
|
||||||
|
this.ws?.close((_err: Error) => {})
|
||||||
|
this.ws = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleReconnect(): void {
|
||||||
|
if (this.destroyed) return
|
||||||
|
const delay = this.reconnectDelay
|
||||||
|
if (SDKContext.getConfig().debug) console.log(`[ImClient] reconnect in ${delay}ms`)
|
||||||
|
this.reconnectTimer = setTimeout(() => {
|
||||||
|
this.connect()
|
||||||
|
this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY)
|
||||||
|
}, delay)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { HttpClient } from '../core/HttpClient'
|
||||||
|
import type { PushTokenInfo } from '../core/Types'
|
||||||
|
|
||||||
|
export class PushSDK {
|
||||||
|
/**
|
||||||
|
* Register a push token with the server.
|
||||||
|
* Call this after obtaining the token from HarmonyOS push service.
|
||||||
|
* vendor should be 'HARMONY' for HarmonyOS devices.
|
||||||
|
*/
|
||||||
|
static async registerToken(token: string, imUserId?: string): Promise<void> {
|
||||||
|
const body: PushTokenInfo = {
|
||||||
|
vendor: 'HARMONY',
|
||||||
|
token,
|
||||||
|
platform: 'harmony',
|
||||||
|
}
|
||||||
|
await HttpClient.post<void>('/api/v1/push/register', {
|
||||||
|
...body,
|
||||||
|
imUserId: imUserId ?? null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister push token on logout.
|
||||||
|
*/
|
||||||
|
static async unregisterToken(token: string): Promise<void> {
|
||||||
|
await HttpClient.post<void>('/api/v1/push/unregister', { token, platform: 'harmony' })
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
import bundleManager from '@ohos.bundle.bundleManager'
|
||||||
|
import request from '@ohos.request'
|
||||||
|
import common from '@ohos.app.ability.common'
|
||||||
|
import { HttpClient } from '../core/HttpClient'
|
||||||
|
import type { AppVersionInfo, RnBundleInfo } from '../core/Types'
|
||||||
|
import { SDKContext } from '../core/SDKContext'
|
||||||
|
|
||||||
|
export interface AppUpdateResult {
|
||||||
|
hasUpdate: boolean
|
||||||
|
info?: AppVersionInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RnUpdateResult {
|
||||||
|
hasUpdate: boolean
|
||||||
|
info?: RnBundleInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateSDK {
|
||||||
|
static async checkAppUpdate(appKey: string): Promise<AppUpdateResult> {
|
||||||
|
const bundleInfo = bundleManager.getBundleInfoForSelfSync(
|
||||||
|
bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT
|
||||||
|
)
|
||||||
|
const currentVersionCode = bundleInfo.versionCode
|
||||||
|
|
||||||
|
const data = await HttpClient.get<AppVersionInfo>(
|
||||||
|
`/api/v1/updates/app/check?appKey=${appKey}&versionCode=${currentVersionCode}&platform=harmony`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.latestVersionCode <= currentVersionCode) {
|
||||||
|
return { hasUpdate: false }
|
||||||
|
}
|
||||||
|
return { hasUpdate: true, info: data }
|
||||||
|
}
|
||||||
|
|
||||||
|
static async checkRnUpdate(
|
||||||
|
appKey: string,
|
||||||
|
bundleName: string,
|
||||||
|
currentBundleVersion: number
|
||||||
|
): Promise<RnUpdateResult> {
|
||||||
|
const data = await HttpClient.get<RnBundleInfo>(
|
||||||
|
`/api/v1/rn/update/check?appKey=${appKey}&bundleName=${bundleName}&bundleVersion=${currentBundleVersion}`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (data.bundleVersion <= currentBundleVersion) {
|
||||||
|
return { hasUpdate: false }
|
||||||
|
}
|
||||||
|
return { hasUpdate: true, info: data }
|
||||||
|
}
|
||||||
|
|
||||||
|
static async downloadRnBundle(
|
||||||
|
context: common.UIAbilityContext,
|
||||||
|
downloadUrl: string,
|
||||||
|
destFilename: string
|
||||||
|
): Promise<string> {
|
||||||
|
const destPath = context.cacheDir + '/' + destFilename
|
||||||
|
await new Promise<void>((resolve, reject) => {
|
||||||
|
request.downloadFile(context, {
|
||||||
|
url: downloadUrl,
|
||||||
|
filePath: destPath,
|
||||||
|
}, (err, task) => {
|
||||||
|
if (err) { reject(err); return }
|
||||||
|
task.on('complete', resolve)
|
||||||
|
task.on('fail', (error: number) => reject(new Error(`Download failed: ${error}`)))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if (SDKContext.getConfig().debug) {
|
||||||
|
console.log('[UpdateSDK] RN bundle downloaded to', destPath)
|
||||||
|
}
|
||||||
|
return destPath
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"module": {
|
||||||
|
"name": "xuqm-sdk",
|
||||||
|
"type": "har",
|
||||||
|
"deviceTypes": ["phone", "tablet"]
|
||||||
|
}
|
||||||
|
}
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户