比较提交
4 次代码提交
930c8f36ae
...
de1c7e77e7
| 作者 | SHA1 | 提交日期 | |
|---|---|---|---|
|
|
de1c7e77e7 | ||
|
|
9e5fa3da03 | ||
|
|
c656bdd202 | ||
|
|
d7f156f160 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -8,3 +8,4 @@ build/
|
|||||||
*.iml
|
*.iml
|
||||||
.idea/
|
.idea/
|
||||||
*.log
|
*.log
|
||||||
|
.hvigor/
|
||||||
|
|||||||
1
.hvigor/cache/file-cache.json
vendored
普通文件
1
.hvigor/cache/file-cache.json
vendored
普通文件
文件差异因一行或多行过长而隐藏
1
.hvigor/cache/meta.json
vendored
普通文件
1
.hvigor/cache/meta.json
vendored
普通文件
@ -0,0 +1 @@
|
|||||||
|
{"compileSdkVersion":"6.0.2(22)","hvigorVersion":"6.22.4","toolChainsVersion":"6.0.2.130"}
|
||||||
1
.hvigor/cache/task-cache.json
vendored
普通文件
1
.hvigor/cache/task-cache.json
vendored
普通文件
文件差异因一行或多行过长而隐藏
@ -0,0 +1 @@
|
|||||||
|
{"basePath":"/Users/xuqinmin/Projects/XuqmGroup/XuqmGroup-HarmonySDK/.hvigor/dependencyMap/dependencyMap.json5","rootDependency":"./oh-package.json5","dependencyMap":{"xuqmSdk":"./xuqmSdk/oh-package.json5","entry":"./entry/oh-package.json5"},"modules":[{"name":"xuqmSdk","srcPath":"../../../xuqm-sdk"},{"name":"entry","srcPath":"../../../entry"}]}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"name":"entry","version":"1.0.0","description":"SDK sample app","main":"","author":"","license":"MIT","dependencies":{"@xuqm/harmony-sdk":"file:../xuqm-sdk"}}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"name":"xuqm-harmony-sdk-workspace","version":"0.1.0","modelVersion":"5.0.0","description":"XuqmGroup HarmonyOS SDK workspace","author":"xuqm","license":"MIT"}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
{"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,69 @@
|
|||||||
|
{
|
||||||
|
"HVIGOR_OHOS_PLUGIN": {
|
||||||
|
"MODULES": [
|
||||||
|
{
|
||||||
|
"MODULE_NAME": "77aabe6c19463543339f337db9c84e4d10fd2f56ea0aedaf85a0214d59e93ec4",
|
||||||
|
"API_TYPE": "stageMode",
|
||||||
|
"INCLUDE_IN_BUILD": true,
|
||||||
|
"IS_COMMAND_LINE_ENTRY_MODULE": false,
|
||||||
|
"MODULE_TYPE": "har",
|
||||||
|
"IS_INCREMENTAL_MODULE": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"MODULE_NAME": "923fe53966c6cd9343e11af776cd4b05be315ea4b200b02e4d5dfb0f929b73bf",
|
||||||
|
"API_TYPE": "stageMode",
|
||||||
|
"INCLUDE_IN_BUILD": true,
|
||||||
|
"IS_COMMAND_LINE_ENTRY_MODULE": false,
|
||||||
|
"MODULE_TYPE": "entry",
|
||||||
|
"INCREMENTAL_TASKS": {
|
||||||
|
"COMPILE_ARKTS": true
|
||||||
|
},
|
||||||
|
"IS_INCREMENTAL_MODULE": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"NATIVE_COMPILER": "Default",
|
||||||
|
"IS_FULL_BUILD": false,
|
||||||
|
"BUILD_MODE": "debug"
|
||||||
|
},
|
||||||
|
"HVIGOR": {
|
||||||
|
"IS_INCREMENTAL": true,
|
||||||
|
"IS_DAEMON": false,
|
||||||
|
"IS_PARALLEL": true,
|
||||||
|
"IS_HVIGORFILE_TYPE_CHECK": false,
|
||||||
|
"TASK_TIME": {
|
||||||
|
"923fe53966c6cd9343e11af776cd4b05be315ea4b200b02e4d5dfb0f929b73bf": {
|
||||||
|
"CreateModuleInfo": 314458,
|
||||||
|
"PreCheckSyscap": 126708,
|
||||||
|
"ProcessIntegratedHsp": 218542,
|
||||||
|
"SyscapTransform": 12138250,
|
||||||
|
"ProcessStartupConfig": 988208,
|
||||||
|
"ConfigureCmake": 73375,
|
||||||
|
"BuildNativeWithCmake": 72625,
|
||||||
|
"BuildNativeWithNinja": 129375,
|
||||||
|
"BuildJS": 920292,
|
||||||
|
"CompileArkTS": 2834291708,
|
||||||
|
"ProcessCompiledResources": 207917,
|
||||||
|
"PackageHap": 234451500,
|
||||||
|
"PackingCheck": 2285000,
|
||||||
|
"SignHap": 493042,
|
||||||
|
"CollectDebugSymbol": 273250,
|
||||||
|
"assembleHap": 56166
|
||||||
|
},
|
||||||
|
"77aabe6c19463543339f337db9c84e4d10fd2f56ea0aedaf85a0214d59e93ec4": {
|
||||||
|
"ConfigureCmake": 64000,
|
||||||
|
"BuildNativeWithCmake": 77000,
|
||||||
|
"BuildNativeWithNinja": 510500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"APIS": [
|
||||||
|
"getProperty"
|
||||||
|
],
|
||||||
|
"CONFIG_EXPERIMENT": {
|
||||||
|
"ENABLE_MODULE_SKIP": false,
|
||||||
|
"ENABLE_CPP_FUNCTION_LEVEL_INCREMENTAL": false
|
||||||
|
},
|
||||||
|
"CONFIG_PROPERTIES": {},
|
||||||
|
"BUILD_ID": "202604282229113860",
|
||||||
|
"TOTAL_TIME": 3981273167
|
||||||
|
}
|
||||||
|
}
|
||||||
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
文件差异内容过多而无法显示
加载差异
10
AppScope/app.json5
普通文件
10
AppScope/app.json5
普通文件
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"bundleName": "com.xuqmgroup.harmony.sdk",
|
||||||
|
"vendor": "xuqm",
|
||||||
|
"versionCode": 1000000,
|
||||||
|
"versionName": "1.0.0",
|
||||||
|
"icon": "$media:app_icon",
|
||||||
|
"label": "$string:EntryAbility_label"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
# XuqmGroup HarmonyOS SDK 文档
|
# XuqmGroup HarmonyOS SDK 文档
|
||||||
|
|
||||||
> ArkTS · HarmonyOS 5 (API 12) · 发布至 ohpm
|
> ArkTS · HarmonyOS 5 (API 12) · 发布至 ohpm
|
||||||
|
> 当前工程已可成功执行 `hvigorw assembleHap`
|
||||||
|
|
||||||
## 模块结构
|
## 模块结构
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,20 @@
|
|||||||
{
|
{
|
||||||
"app": {
|
"app": {
|
||||||
"signingConfigs": [],
|
"signingConfigs": [
|
||||||
|
{
|
||||||
|
"name": "default",
|
||||||
|
"type": "HarmonyOS",
|
||||||
|
"material": {
|
||||||
|
"certpath": "/Users/xuqinmin/.ohos/config/default_XuqmGroup-HarmonySDK_xvtPiZRWbWuJ1guqtszanEOmoD8f2kda5ume6x7cDEg=.cer",
|
||||||
|
"keyAlias": "debugKey",
|
||||||
|
"keyPassword": "0000001BAEFFFD913AED357759CBA7F92502E226B07F444BBB101F06934FC8D4A84C1B5561BC0253532FCC",
|
||||||
|
"profile": "/Users/xuqinmin/.ohos/config/default_XuqmGroup-HarmonySDK_xvtPiZRWbWuJ1guqtszanEOmoD8f2kda5ume6x7cDEg=.p7b",
|
||||||
|
"signAlg": "SHA256withECDSA",
|
||||||
|
"storeFile": "/Users/xuqinmin/.ohos/config/default_XuqmGroup-HarmonySDK_xvtPiZRWbWuJ1guqtszanEOmoD8f2kda5ume6x7cDEg=.p12",
|
||||||
|
"storePassword": "0000001B822DC06852C049293C0D7A3A5ED4C040F995F0DE8E832EA7C75D5C0B9A063C501AE3B14CE6B900"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"products": [
|
"products": [
|
||||||
{
|
{
|
||||||
"name": "default",
|
"name": "default",
|
||||||
@ -12,27 +26,42 @@
|
|||||||
"caseSensitiveCheck": true,
|
"caseSensitiveCheck": true,
|
||||||
"useNormalizedOHMUrl": true
|
"useNormalizedOHMUrl": true
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"targetSdkVersion": "6.0.2(22)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"buildModeSet": [
|
"buildModeSet": [
|
||||||
{ "name": "debug" },
|
{
|
||||||
{ "name": "release" }
|
"name": "debug"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "release"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "xuqm-sdk",
|
"name": "xuqmSdk",
|
||||||
"srcPath": "./xuqm-sdk",
|
"srcPath": "./xuqm-sdk",
|
||||||
"targets": [
|
"targets": [
|
||||||
{ "name": "default", "applyToProducts": ["default"] }
|
{
|
||||||
|
"name": "default",
|
||||||
|
"applyToProducts": [
|
||||||
|
"default"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "entry",
|
"name": "entry",
|
||||||
"srcPath": "./entry",
|
"srcPath": "./entry",
|
||||||
"targets": [
|
"targets": [
|
||||||
{ "name": "default", "applyToProducts": ["default"] }
|
{
|
||||||
|
"name": "default",
|
||||||
|
"applyToProducts": [
|
||||||
|
"default"
|
||||||
|
]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
19
entry/oh-package-lock.json5
普通文件
19
entry/oh-package-lock.json5
普通文件
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"meta": {
|
||||||
|
"stableOrder": true,
|
||||||
|
"enableUnifiedLockfile": false
|
||||||
|
},
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
|
||||||
|
"specifiers": {
|
||||||
|
"@xuqm/harmony-sdk@../xuqm-sdk": "@xuqm/harmony-sdk@../xuqm-sdk"
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@xuqm/harmony-sdk@../xuqm-sdk": {
|
||||||
|
"name": "@xuqm/harmony-sdk",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "../xuqm-sdk",
|
||||||
|
"registryType": "local"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1 @@
|
|||||||
|
../../../xuqm-sdk
|
||||||
@ -1,6 +1,42 @@
|
|||||||
import { XuqmSDK, ImMessage } from '@xuqm/harmony-sdk'
|
import { XuqmSDK, ImMessage } from '@xuqm/harmony-sdk'
|
||||||
|
import type { ImEventDelegate } from '@xuqm/harmony-sdk'
|
||||||
import promptAction from '@ohos.promptAction'
|
import promptAction from '@ohos.promptAction'
|
||||||
|
|
||||||
|
class DemoImDelegate implements ImEventDelegate {
|
||||||
|
private readonly onConnectedAction: () => void
|
||||||
|
private readonly onDisconnectedAction: (code: number, reason: string) => void
|
||||||
|
private readonly onMessageAction: (msg: ImMessage) => void
|
||||||
|
private readonly onErrorAction: (message: string) => void
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
onConnectedAction: () => void,
|
||||||
|
onDisconnectedAction: (code: number, reason: string) => void,
|
||||||
|
onMessageAction: (msg: ImMessage) => void,
|
||||||
|
onErrorAction: (message: string) => void,
|
||||||
|
) {
|
||||||
|
this.onConnectedAction = onConnectedAction
|
||||||
|
this.onDisconnectedAction = onDisconnectedAction
|
||||||
|
this.onMessageAction = onMessageAction
|
||||||
|
this.onErrorAction = onErrorAction
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnected(): void {
|
||||||
|
this.onConnectedAction()
|
||||||
|
}
|
||||||
|
|
||||||
|
onDisconnected(code: number, reason: string): void {
|
||||||
|
this.onDisconnectedAction(code, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMessage(msg: ImMessage): void {
|
||||||
|
this.onMessageAction(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
onError(message: string): void {
|
||||||
|
this.onErrorAction(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Entry
|
@Entry
|
||||||
@Component
|
@Component
|
||||||
struct Index {
|
struct Index {
|
||||||
@ -11,22 +47,23 @@ struct Index {
|
|||||||
|
|
||||||
aboutToAppear(): void {
|
aboutToAppear(): void {
|
||||||
const im = XuqmSDK.im
|
const im = XuqmSDK.im
|
||||||
im.delegate = {
|
const delegate = new DemoImDelegate(
|
||||||
onConnected: () => {
|
() => {
|
||||||
this.connected = true
|
this.connected = true
|
||||||
promptAction.showToast({ message: 'IM 已连接' })
|
promptAction.showToast({ message: 'IM 已连接' })
|
||||||
},
|
},
|
||||||
onDisconnected: (code, reason) => {
|
(code: number, reason: string) => {
|
||||||
this.connected = false
|
this.connected = false
|
||||||
console.log(`IM disconnected: ${code} ${reason}`)
|
console.log('IM disconnected: ' + code + ' ' + reason)
|
||||||
},
|
},
|
||||||
onMessage: (msg) => {
|
(msg: ImMessage) => {
|
||||||
this.messages = [...this.messages, msg]
|
this.messages = [...this.messages, msg]
|
||||||
},
|
},
|
||||||
onError: (err) => {
|
(err: string) => {
|
||||||
promptAction.showToast({ message: 'IM 错误: ' + err })
|
promptAction.showToast({ message: 'IM 错误: ' + err })
|
||||||
},
|
},
|
||||||
}
|
)
|
||||||
|
im.delegate = delegate
|
||||||
im.connect()
|
im.connect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
二进制文件未显示。
|
之后 宽度: | 高度: | 大小: 68 B |
19
hvigor/hvigor-config.json5
普通文件
19
hvigor/hvigor-config.json5
普通文件
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"modelVersion": "5.0.0",
|
||||||
|
"dependencies": {},
|
||||||
|
"execution": {
|
||||||
|
"daemon": false,
|
||||||
|
"incremental": true,
|
||||||
|
"parallel": true,
|
||||||
|
"typeCheck": false
|
||||||
|
},
|
||||||
|
"logging": {
|
||||||
|
"level": "info"
|
||||||
|
},
|
||||||
|
"debugging": {
|
||||||
|
"stacktrace": false
|
||||||
|
},
|
||||||
|
"nodeOptions": {
|
||||||
|
"maxOldSpaceSize": 4096
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "xuqm-harmony-sdk-workspace",
|
"name": "xuqm-harmony-sdk-workspace",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"modelVersion": "5.0.0",
|
||||||
"description": "XuqmGroup HarmonyOS SDK workspace",
|
"description": "XuqmGroup HarmonyOS SDK workspace",
|
||||||
"author": "xuqm",
|
"author": "xuqm",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
|
|||||||
48
oh_modules/.ohpm/lock.json5
普通文件
48
oh_modules/.ohpm/lock.json5
普通文件
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"lockVersion": "1.0",
|
||||||
|
"settings": {
|
||||||
|
"resolveConflict": true,
|
||||||
|
"resolveConflictStrict": false,
|
||||||
|
"installAll": true
|
||||||
|
},
|
||||||
|
"overrides": {},
|
||||||
|
"overrideDependencyMap": {},
|
||||||
|
"modules": {
|
||||||
|
".": {
|
||||||
|
"name": "",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {},
|
||||||
|
"dynamicDependencies": {},
|
||||||
|
"maskedByOverrideDependencyMap": false
|
||||||
|
},
|
||||||
|
"entry": {
|
||||||
|
"name": "entry",
|
||||||
|
"dependencies": {
|
||||||
|
"@xuqm/harmony-sdk": {
|
||||||
|
"specifier": "file:xuqm-sdk",
|
||||||
|
"version": "file:xuqm-sdk"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"dynamicDependencies": {},
|
||||||
|
"maskedByOverrideDependencyMap": false
|
||||||
|
},
|
||||||
|
"xuqm-sdk": {
|
||||||
|
"name": "xuqmSdk",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {},
|
||||||
|
"dynamicDependencies": {},
|
||||||
|
"maskedByOverrideDependencyMap": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@xuqm/harmony-sdk@file:xuqm-sdk": {
|
||||||
|
"storePath": "xuqm-sdk",
|
||||||
|
"dependencies": {},
|
||||||
|
"dynamicDependencies": {},
|
||||||
|
"dev": false,
|
||||||
|
"dynamic": false,
|
||||||
|
"maskedByOverrideDependencyMap": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
xuqm-sdk/BuildProfile.ets
普通文件
17
xuqm-sdk/BuildProfile.ets
普通文件
@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Use these variables when you tailor your ArkTS code. They must be of the const type.
|
||||||
|
*/
|
||||||
|
export const HAR_VERSION = '0.1.0';
|
||||||
|
export const BUILD_MODE_NAME = 'debug';
|
||||||
|
export const DEBUG = true;
|
||||||
|
export const TARGET_NAME = 'default';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BuildProfile Class is used only for compatibility purposes.
|
||||||
|
*/
|
||||||
|
export default class BuildProfile {
|
||||||
|
static readonly HAR_VERSION = HAR_VERSION;
|
||||||
|
static readonly BUILD_MODE_NAME = BUILD_MODE_NAME;
|
||||||
|
static readonly DEBUG = DEBUG;
|
||||||
|
static readonly TARGET_NAME = TARGET_NAME;
|
||||||
|
}
|
||||||
@ -15,6 +15,9 @@ export type {
|
|||||||
ChatType,
|
ChatType,
|
||||||
MsgStatus,
|
MsgStatus,
|
||||||
ConversationData,
|
ConversationData,
|
||||||
|
FriendRequest,
|
||||||
|
GroupJoinRequest,
|
||||||
|
ImGroup,
|
||||||
HistoryQuery,
|
HistoryQuery,
|
||||||
PageResult,
|
PageResult,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
|||||||
@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* XuqmGroup Update Service — HarmonyOS hvigorw Release Task
|
||||||
|
*
|
||||||
|
* HarmonyOS projects use hvigorw (Huawei's Gradle wrapper).
|
||||||
|
* This script registers a task compatible with both hvigorw and standard Gradle.
|
||||||
|
*
|
||||||
|
* Copy to your HarmonyOS module directory and apply:
|
||||||
|
* apply(from = "xuqm_release.gradle.kts") // in build.gradle.kts
|
||||||
|
*
|
||||||
|
* Run:
|
||||||
|
* ./hvigorw xuqmRelease --mode project
|
||||||
|
*
|
||||||
|
* Config: xuqm.properties in the module or project root
|
||||||
|
* ---
|
||||||
|
* xuqm.serverUrl=https://update.dev.xuqinmin.com
|
||||||
|
* xuqm.appId=your-app-id
|
||||||
|
* xuqm.apiToken=your-api-token
|
||||||
|
* xuqm.storeTargets=HUAWEI # optional: HUAWEI,HONOR,...
|
||||||
|
* xuqm.autoPublishAfterReview=false
|
||||||
|
* xuqm.scheduledPublishAt= # optional ISO datetime
|
||||||
|
* xuqm.webhookUrl= # optional
|
||||||
|
* ---
|
||||||
|
*
|
||||||
|
* Version is read from AppScope/app.json5 (standard HarmonyOS project layout).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.gradle.api.GradleException
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.http.HttpClient
|
||||||
|
import java.net.http.HttpRequest
|
||||||
|
import java.net.http.HttpResponse
|
||||||
|
import java.util.Properties
|
||||||
|
import java.util.UUID
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
// ── Config ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fun loadXuqmCfg(projectDir: File): Properties {
|
||||||
|
val props = Properties()
|
||||||
|
listOf(File(projectDir, "xuqm.properties"), File(projectDir.parentFile, "xuqm.properties"))
|
||||||
|
.firstOrNull { it.exists() }?.inputStream()?.use(props::load)
|
||||||
|
?: throw GradleException("xuqm.properties not found")
|
||||||
|
return props
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Version from app.json5 ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
data class HarmonyVersion(val name: String, val code: Int)
|
||||||
|
|
||||||
|
fun readHarmonyVersion(projectDir: File): HarmonyVersion {
|
||||||
|
val json5 = listOf(
|
||||||
|
File(projectDir, "AppScope/app.json5"),
|
||||||
|
File(projectDir.parentFile, "AppScope/app.json5"),
|
||||||
|
).firstOrNull { it.exists() } ?: throw GradleException("AppScope/app.json5 not found")
|
||||||
|
|
||||||
|
val content = json5.readText()
|
||||||
|
val name = Regex(""""versionName"\s*:\s*"([^"]+)"""").find(content)?.groupValues?.get(1)
|
||||||
|
?: throw GradleException("versionName not found in app.json5")
|
||||||
|
val code = Regex(""""versionCode"\s*:\s*(\d+)""").find(content)?.groupValues?.get(1)?.toInt()
|
||||||
|
?: throw GradleException("versionCode not found in app.json5")
|
||||||
|
return HarmonyVersion(name, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── HTTP helpers ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fun httpGet(url: String, token: String): String {
|
||||||
|
val client = HttpClient.newHttpClient()
|
||||||
|
val req = HttpRequest.newBuilder(URI.create(url)).header("Authorization", "Bearer $token").GET().build()
|
||||||
|
return client.send(req, HttpResponse.BodyHandlers.ofString()).body()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun httpMultipartPost(url: String, token: String, parts: Map<String, Any>): String {
|
||||||
|
val boundary = UUID.randomUUID().toString()
|
||||||
|
val baos = java.io.ByteArrayOutputStream()
|
||||||
|
fun writeln(s: String) = baos.write("$s\r\n".toByteArray())
|
||||||
|
for ((name, value) in parts) {
|
||||||
|
writeln("--$boundary")
|
||||||
|
when (value) {
|
||||||
|
is File -> {
|
||||||
|
writeln("""Content-Disposition: form-data; name="$name"; filename="${value.name}"""")
|
||||||
|
writeln("Content-Type: application/octet-stream"); writeln("")
|
||||||
|
baos.write(value.readBytes()); writeln("")
|
||||||
|
}
|
||||||
|
else -> { writeln("""Content-Disposition: form-data; name="$name""""); writeln(""); writeln(value.toString()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writeln("--$boundary--")
|
||||||
|
val client = HttpClient.newHttpClient()
|
||||||
|
val req = HttpRequest.newBuilder(URI.create(url))
|
||||||
|
.header("Authorization", "Bearer $token")
|
||||||
|
.header("Content-Type", "multipart/form-data; boundary=$boundary")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofByteArray(baos.toByteArray()))
|
||||||
|
.build()
|
||||||
|
return client.send(req, HttpResponse.BodyHandlers.ofString()).body()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseJson(json: String, key: String) =
|
||||||
|
Regex(""""$key"\s*:\s*"([^"]+)"""").find(json)?.groupValues?.get(1)
|
||||||
|
|
||||||
|
// ── Task ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
tasks.register("xuqmRelease") {
|
||||||
|
group = "xuqm"
|
||||||
|
description = "Build HAP and upload to XuqmGroup Update Service"
|
||||||
|
|
||||||
|
// hvigorw uses 'assembleApp' by default; adjust if your task is named differently
|
||||||
|
dependsOn(tasks.findByName("assembleApp") ?: tasks.findByName("default") ?: return@register)
|
||||||
|
|
||||||
|
doLast {
|
||||||
|
val cfg = loadXuqmCfg(projectDir)
|
||||||
|
val serverUrl = cfg.getProperty("xuqm.serverUrl") ?: throw GradleException("xuqm.serverUrl missing")
|
||||||
|
val appId = cfg.getProperty("xuqm.appId") ?: throw GradleException("xuqm.appId missing")
|
||||||
|
val apiToken = cfg.getProperty("xuqm.apiToken") ?: throw GradleException("xuqm.apiToken missing")
|
||||||
|
val storeTargets = cfg.getProperty("xuqm.storeTargets", "")
|
||||||
|
val autoPublish = cfg.getProperty("xuqm.autoPublishAfterReview", "false").toBoolean()
|
||||||
|
val scheduledAt = cfg.getProperty("xuqm.scheduledPublishAt", "")
|
||||||
|
val webhookUrl = cfg.getProperty("xuqm.webhookUrl", "")
|
||||||
|
|
||||||
|
// ── 1. Read local version ──────────────────────────────────────────
|
||||||
|
val (versionName, versionCode) = readHarmonyVersion(projectDir)
|
||||||
|
println("[xuqm] Local version: $versionName ($versionCode)")
|
||||||
|
|
||||||
|
// ── 2. Check server ────────────────────────────────────────────────
|
||||||
|
val listResp = httpGet("$serverUrl/api/v1/updates/app/list?appId=$appId&platform=ANDROID", apiToken)
|
||||||
|
val serverCode = Regex(""""versionCode"\s*:\s*(\d+)""").findAll(listResp)
|
||||||
|
.mapNotNull { it.groupValues[1].toIntOrNull() }.maxOrNull() ?: 0
|
||||||
|
println("[xuqm] Server latest versionCode: $serverCode")
|
||||||
|
|
||||||
|
if (versionCode <= serverCode) {
|
||||||
|
throw GradleException(
|
||||||
|
"[xuqm] Local versionCode ($versionCode) ≤ server ($serverCode). " +
|
||||||
|
"Bump versionCode in AppScope/app.json5 first."
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── 3. Locate HAP ──────────────────────────────────────────────────
|
||||||
|
val hapDir = File(projectDir, "entry/build/default/outputs/default")
|
||||||
|
val hapFile = hapDir.listFiles { f -> f.extension == "hap" }?.firstOrNull()
|
||||||
|
?: throw GradleException("HAP not found in ${hapDir.absolutePath}")
|
||||||
|
println("[xuqm] HAP: ${hapFile.absolutePath}")
|
||||||
|
|
||||||
|
// ── 4. Upload ──────────────────────────────────────────────────────
|
||||||
|
val parts = mutableMapOf<String, Any>(
|
||||||
|
"appId" to appId, "platform" to "ANDROID",
|
||||||
|
"versionName" to versionName, "versionCode" to versionCode,
|
||||||
|
"forceUpdate" to "false", "autoPublishAfterReview" to autoPublish.toString(),
|
||||||
|
"apkFile" to hapFile,
|
||||||
|
)
|
||||||
|
if (storeTargets.isNotBlank()) parts["storeSubmitTargets"] = "[\"${storeTargets.split(",").joinToString("\",\"")}\"]"
|
||||||
|
if (scheduledAt.isNotBlank()) parts["scheduledPublishAt"] = scheduledAt
|
||||||
|
if (webhookUrl.isNotBlank()) parts["webhookUrl"] = webhookUrl
|
||||||
|
|
||||||
|
println("[xuqm] Uploading HAP...")
|
||||||
|
val uploadResp = httpMultipartPost("$serverUrl/api/v1/updates/app/upload", apiToken, parts)
|
||||||
|
val versionId = parseJson(uploadResp, "id")
|
||||||
|
?: throw GradleException("[xuqm] Upload failed:\n$uploadResp")
|
||||||
|
println("[xuqm] Uploaded, version ID: $versionId")
|
||||||
|
|
||||||
|
// ── 5. Trigger store submission ────────────────────────────────────
|
||||||
|
if (storeTargets.isNotBlank()) {
|
||||||
|
println("[xuqm] Triggering store submission: $storeTargets")
|
||||||
|
val body = """{"storeTypes":[${storeTargets.split(",").joinToString(",") { "\"$it\"" }}]}"""
|
||||||
|
val client = HttpClient.newHttpClient()
|
||||||
|
val req = HttpRequest.newBuilder(URI.create("$serverUrl/api/v1/updates/store/app/$versionId/execute-submit"))
|
||||||
|
.header("Authorization", "Bearer $apiToken")
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.POST(HttpRequest.BodyPublishers.ofString(body)).build()
|
||||||
|
val storeResp = client.send(req, HttpResponse.BodyHandlers.ofString())
|
||||||
|
println("[xuqm] Store submission HTTP ${storeResp.statusCode()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
println("[xuqm] Done.")
|
||||||
|
if (scheduledAt.isNotBlank()) {
|
||||||
|
println("[xuqm] Will publish at: $scheduledAt")
|
||||||
|
} else if (autoPublish) {
|
||||||
|
println("[xuqm] Will auto-publish after store reviews pass.")
|
||||||
|
} else {
|
||||||
|
println("[xuqm] Publish manually: POST $serverUrl/api/v1/updates/app/$versionId/publish")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,8 +2,6 @@ import common from '@ohos.app.ability.common'
|
|||||||
import type { SDKConfig } from './core/Types'
|
import type { SDKConfig } from './core/Types'
|
||||||
import { SDKContext } from './core/SDKContext'
|
import { SDKContext } from './core/SDKContext'
|
||||||
import { ImClient } from './im/ImClient'
|
import { ImClient } from './im/ImClient'
|
||||||
import { PushSDK } from './push/PushSDK'
|
|
||||||
import { UpdateSDK } from './update/UpdateSDK'
|
|
||||||
|
|
||||||
export class XuqmSDK {
|
export class XuqmSDK {
|
||||||
private static _imClient: ImClient | null = null
|
private static _imClient: ImClient | null = null
|
||||||
@ -35,12 +33,4 @@ export class XuqmSDK {
|
|||||||
}
|
}
|
||||||
return XuqmSDK._imClient
|
return XuqmSDK._imClient
|
||||||
}
|
}
|
||||||
|
|
||||||
static get push(): typeof PushSDK {
|
|
||||||
return PushSDK
|
|
||||||
}
|
|
||||||
|
|
||||||
static get update(): typeof UpdateSDK {
|
|
||||||
return UpdateSDK
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,35 +1,30 @@
|
|||||||
import http from '@ohos.net.http'
|
import http from '@ohos.net.http'
|
||||||
import type { ApiResponse } from './Types'
|
import type { ApiResponse, HttpHeaders } from './Types'
|
||||||
import { SDKContext } from './SDKContext'
|
import { SDKContext } from './SDKContext'
|
||||||
|
|
||||||
export class HttpClient {
|
export class HttpClient {
|
||||||
static async request<T>(
|
static async request<T>(
|
||||||
method: http.RequestMethod,
|
method: http.RequestMethod,
|
||||||
path: string,
|
path: string,
|
||||||
body?: object,
|
body?: Object,
|
||||||
query?: Record<string, string | number | boolean | Date | null | undefined>,
|
query?: string,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const config = SDKContext.getConfig()
|
const config = SDKContext.getConfig()
|
||||||
const token = SDKContext.getToken()
|
const token = SDKContext.getToken()
|
||||||
const queryPairs: string[] = []
|
const url = config.apiBaseUrl.replace(/\/$/, '') + path + (query ? '?' + query : '')
|
||||||
if (query) {
|
|
||||||
for (const key of Object.keys(query)) {
|
|
||||||
const value = query[key]
|
|
||||||
if (value === undefined || value === null || value === '') continue
|
|
||||||
queryPairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value instanceof Date ? value.toISOString() : String(value))}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const url = config.apiBaseUrl.replace(/\/$/, '') + path + (queryPairs.length > 0 ? `?${queryPairs.join('&')}` : '')
|
|
||||||
|
|
||||||
const client = http.createHttp()
|
const client = http.createHttp()
|
||||||
try {
|
try {
|
||||||
|
const header: HttpHeaders = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
if (token) {
|
||||||
|
header.Authorization = 'Bearer ' + token
|
||||||
|
}
|
||||||
const options: http.HttpRequestOptions = {
|
const options: http.HttpRequestOptions = {
|
||||||
method,
|
method,
|
||||||
header: {
|
header,
|
||||||
'Content-Type': 'application/json',
|
extraData: body ? JSON.stringify(body) : '',
|
||||||
...(token ? { 'Authorization': `Bearer ${token}` } : {}),
|
|
||||||
},
|
|
||||||
extraData: body ? JSON.stringify(body) : undefined,
|
|
||||||
connectTimeout: 15000,
|
connectTimeout: 15000,
|
||||||
readTimeout: 15000,
|
readTimeout: 15000,
|
||||||
}
|
}
|
||||||
@ -43,19 +38,19 @@ export class HttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static get<T>(path: string, query?: Record<string, string | number | boolean | Date | null | undefined>): Promise<T> {
|
static get<T>(path: string, query?: string): Promise<T> {
|
||||||
return HttpClient.request<T>(http.RequestMethod.GET, path, undefined, query)
|
return HttpClient.request<T>(http.RequestMethod.GET, path, undefined, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static post<T>(path: string, body?: object, query?: Record<string, string | number | boolean | Date | null | undefined>): Promise<T> {
|
static post<T>(path: string, body?: Object, query?: string): Promise<T> {
|
||||||
return HttpClient.request<T>(http.RequestMethod.POST, path, body, query)
|
return HttpClient.request<T>(http.RequestMethod.POST, path, body, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static put<T>(path: string, body?: object, query?: Record<string, string | number | boolean | Date | null | undefined>): Promise<T> {
|
static put<T>(path: string, body?: Object, query?: string): Promise<T> {
|
||||||
return HttpClient.request<T>(http.RequestMethod.PUT, path, body, query)
|
return HttpClient.request<T>(http.RequestMethod.PUT, path, body, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
static delete<T>(path: string, query?: Record<string, string | number | boolean | Date | null | undefined>): Promise<T> {
|
static delete<T>(path: string, query?: string): Promise<T> {
|
||||||
return HttpClient.request<T>(http.RequestMethod.DELETE, path, undefined, query)
|
return HttpClient.request<T>(http.RequestMethod.DELETE, path, undefined, query)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,11 @@ export interface ApiResponse<T> {
|
|||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HttpHeaders {
|
||||||
|
'Content-Type': string
|
||||||
|
Authorization?: string
|
||||||
|
}
|
||||||
|
|
||||||
export type MsgType =
|
export type MsgType =
|
||||||
| 'TEXT'
|
| 'TEXT'
|
||||||
| 'IMAGE'
|
| 'IMAGE'
|
||||||
@ -93,6 +98,40 @@ export interface UserProfile {
|
|||||||
createdAt?: number | null
|
createdAt?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImGroup {
|
||||||
|
id: string
|
||||||
|
appId?: string
|
||||||
|
name: string
|
||||||
|
groupType?: string
|
||||||
|
creatorId: string
|
||||||
|
memberIds: string
|
||||||
|
adminIds: string
|
||||||
|
announcement?: string | null
|
||||||
|
createdAt?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FriendRequest {
|
||||||
|
id: string
|
||||||
|
appId?: string
|
||||||
|
fromUserId: string
|
||||||
|
toUserId: string
|
||||||
|
remark?: string | null
|
||||||
|
status: 'PENDING' | 'ACCEPTED' | 'REJECTED'
|
||||||
|
createdAt: number
|
||||||
|
reviewedAt?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GroupJoinRequest {
|
||||||
|
id: string
|
||||||
|
appId?: string
|
||||||
|
groupId: string
|
||||||
|
requesterId: string
|
||||||
|
remark?: string | null
|
||||||
|
status: 'PENDING' | 'ACCEPTED' | 'REJECTED'
|
||||||
|
createdAt: number
|
||||||
|
reviewedAt?: number | null
|
||||||
|
}
|
||||||
|
|
||||||
export interface SendMessageParams {
|
export interface SendMessageParams {
|
||||||
messageId?: string
|
messageId?: string
|
||||||
toId: string
|
toId: string
|
||||||
|
|||||||
@ -1,12 +1,25 @@
|
|||||||
import webSocket from '@ohos.net.webSocket'
|
import webSocket from '@ohos.net.webSocket'
|
||||||
import { HttpClient } from '../core/HttpClient'
|
import { HttpClient } from '../core/HttpClient'
|
||||||
import { SDKContext } from '../core/SDKContext'
|
import { SDKContext } from '../core/SDKContext'
|
||||||
import type { ChatType, ConversationData, HistoryQuery, ImMessage, MsgType, PageResult, SendMessageParams, UserProfile } from '../core/Types'
|
import type {
|
||||||
|
ChatType,
|
||||||
|
ConversationData,
|
||||||
|
FriendRequest,
|
||||||
|
GroupJoinRequest,
|
||||||
|
HistoryQuery,
|
||||||
|
ImGroup,
|
||||||
|
ImMessage,
|
||||||
|
MsgType,
|
||||||
|
PageResult,
|
||||||
|
SendMessageParams,
|
||||||
|
UserProfile,
|
||||||
|
} from '../core/Types'
|
||||||
|
|
||||||
export interface ImEventDelegate {
|
export interface ImEventDelegate {
|
||||||
onConnected?(): void
|
onConnected?(): void
|
||||||
onDisconnected?(code: number, reason: string): void
|
onDisconnected?(code: number, reason: string): void
|
||||||
onMessage?(msg: ImMessage): void
|
onMessage?(msg: ImMessage): void
|
||||||
|
onRead?(msg: ImMessage): void
|
||||||
onRevoke?(data: RevokeData): void
|
onRevoke?(data: RevokeData): void
|
||||||
onError?(message: string): void
|
onError?(message: string): void
|
||||||
}
|
}
|
||||||
@ -16,6 +29,93 @@ export interface RevokeData {
|
|||||||
operatorId: string
|
operatorId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class WebSocketFrame {
|
||||||
|
type: string = ''
|
||||||
|
payload: Object = new Object()
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebSocketEnvelope {
|
||||||
|
destination: string = ''
|
||||||
|
payload: Object = new Object()
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppBody {
|
||||||
|
appId: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class HistoryQueryParams {
|
||||||
|
appId: string = ''
|
||||||
|
page: number = 0
|
||||||
|
size: number = 0
|
||||||
|
msgType: MsgType = 'TEXT'
|
||||||
|
keyword: string = ''
|
||||||
|
startTime: string = ''
|
||||||
|
endTime: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConversationActionBody {
|
||||||
|
appId: string = ''
|
||||||
|
chatType: ChatType = 'SINGLE'
|
||||||
|
}
|
||||||
|
|
||||||
|
class FriendRequestQueryBody {
|
||||||
|
appId: string = ''
|
||||||
|
direction: 'incoming' | 'outgoing' = 'incoming'
|
||||||
|
}
|
||||||
|
|
||||||
|
class FriendRequestBody {
|
||||||
|
appId: string = ''
|
||||||
|
toUserId: string = ''
|
||||||
|
remark: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class GroupJoinRequestBody {
|
||||||
|
appId: string = ''
|
||||||
|
remark: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditMessageBody {
|
||||||
|
content: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class UpdateProfileBody {
|
||||||
|
appId: string = ''
|
||||||
|
nickname: string = ''
|
||||||
|
avatar: string = ''
|
||||||
|
gender: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class DraftBody {
|
||||||
|
appId: string = ''
|
||||||
|
chatType: ChatType = 'SINGLE'
|
||||||
|
draft: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class PinBody {
|
||||||
|
appId: string = ''
|
||||||
|
chatType: ChatType = 'SINGLE'
|
||||||
|
pinned: boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
class MuteBody {
|
||||||
|
appId: string = ''
|
||||||
|
chatType: ChatType = 'SINGLE'
|
||||||
|
muted: boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
class SendEnvelopePayload {
|
||||||
|
messageId: string = ''
|
||||||
|
toId: string = ''
|
||||||
|
chatType: ChatType = 'SINGLE'
|
||||||
|
msgType: MsgType = 'TEXT'
|
||||||
|
content: string = ''
|
||||||
|
mentionedUserIds: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
class RevokeEnvelopePayload {
|
||||||
|
msgId: string = ''
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_RECONNECT_DELAY = 30_000
|
const MAX_RECONNECT_DELAY = 30_000
|
||||||
|
|
||||||
export class ImClient {
|
export class ImClient {
|
||||||
@ -29,7 +129,7 @@ export class ImClient {
|
|||||||
if (this.destroyed) return
|
if (this.destroyed) return
|
||||||
const config = SDKContext.getConfig()
|
const config = SDKContext.getConfig()
|
||||||
const token = SDKContext.getToken() ?? ''
|
const token = SDKContext.getToken() ?? ''
|
||||||
const url = `${config.imBaseUrl}/ws/im?token=${token}`
|
const url = config.imBaseUrl.replace(/\/$/, '') + '/ws/im?token=' + encodeURIComponent(token)
|
||||||
|
|
||||||
this.ws = webSocket.createWebSocket()
|
this.ws = webSocket.createWebSocket()
|
||||||
|
|
||||||
@ -41,10 +141,19 @@ export class ImClient {
|
|||||||
|
|
||||||
this.ws.on('message', (_err: Error, value: string | ArrayBuffer) => {
|
this.ws.on('message', (_err: Error, value: string | ArrayBuffer) => {
|
||||||
try {
|
try {
|
||||||
const text = typeof value === 'string' ? value : new TextDecoder().decode(value)
|
if (typeof value !== 'string') {
|
||||||
const frame = JSON.parse(text) as { type: string; payload: unknown }
|
return
|
||||||
|
}
|
||||||
|
const frame = JSON.parse(value) as WebSocketFrame
|
||||||
if (frame.type === 'MESSAGE') {
|
if (frame.type === 'MESSAGE') {
|
||||||
this.delegate?.onMessage?.(this.normalizeMessage(frame.payload as ImMessage))
|
const message = this.normalizeMessage(frame.payload as ImMessage)
|
||||||
|
if (message.status === 'READ') {
|
||||||
|
this.delegate?.onRead?.(message)
|
||||||
|
}
|
||||||
|
if (message.revoked || message.status === 'REVOKED' || message.msgType === 'REVOKED') {
|
||||||
|
this.delegate?.onRevoke?.({ msgId: message.id, operatorId: message.fromId })
|
||||||
|
}
|
||||||
|
this.delegate?.onMessage?.(message)
|
||||||
} else if (frame.type === 'REVOKE') {
|
} else if (frame.type === 'REVOKE') {
|
||||||
this.delegate?.onRevoke?.(frame.payload as RevokeData)
|
this.delegate?.onRevoke?.(frame.payload as RevokeData)
|
||||||
}
|
}
|
||||||
@ -68,23 +177,27 @@ export class ImClient {
|
|||||||
send(params: SendMessageParams): ImMessage {
|
send(params: SendMessageParams): ImMessage {
|
||||||
const outgoing = this.buildOutgoingMessage(params)
|
const outgoing = this.buildOutgoingMessage(params)
|
||||||
if (!this.ws) {
|
if (!this.ws) {
|
||||||
return { ...outgoing, status: 'FAILED' }
|
return this.markFailed(outgoing)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.ws.send(
|
const payload = new SendEnvelopePayload()
|
||||||
JSON.stringify({
|
payload.messageId = outgoing.id
|
||||||
destination: '/app/chat.send',
|
payload.toId = params.toId
|
||||||
payload: {
|
payload.chatType = params.chatType
|
||||||
...params,
|
payload.msgType = params.msgType
|
||||||
messageId: outgoing.id,
|
payload.content = params.content
|
||||||
},
|
if (params.mentionedUserIds !== undefined) {
|
||||||
}),
|
payload.mentionedUserIds = params.mentionedUserIds
|
||||||
(_err: Error) => {
|
}
|
||||||
|
const envelope = new WebSocketEnvelope()
|
||||||
|
envelope.destination = '/app/chat.send'
|
||||||
|
envelope.payload = payload
|
||||||
|
|
||||||
|
this.ws.send(JSON.stringify(envelope), (_err: Error) => {
|
||||||
if (_err) {
|
if (_err) {
|
||||||
this.delegate?.onError?.(_err.message)
|
this.delegate?.onError?.(_err.message)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
|
||||||
return outgoing
|
return outgoing
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,15 +205,14 @@ export class ImClient {
|
|||||||
if (!this.ws) {
|
if (!this.ws) {
|
||||||
throw new Error('WebSocket not connected')
|
throw new Error('WebSocket not connected')
|
||||||
}
|
}
|
||||||
this.ws.send(
|
const payload = new RevokeEnvelopePayload()
|
||||||
JSON.stringify({
|
payload.msgId = msgId
|
||||||
destination: '/app/chat.revoke',
|
const envelope = new WebSocketEnvelope()
|
||||||
payload: { msgId },
|
envelope.destination = '/app/chat.revoke'
|
||||||
}),
|
envelope.payload = payload
|
||||||
(_err: Error) => {
|
this.ws.send(JSON.stringify(envelope), (_err: Error) => {
|
||||||
if (_err) this.delegate?.onError?.(_err.message)
|
if (_err) this.delegate?.onError?.(_err.message)
|
||||||
},
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchHistory(
|
async fetchHistory(
|
||||||
@ -109,19 +221,8 @@ export class ImClient {
|
|||||||
size: number = 20,
|
size: number = 20,
|
||||||
query: HistoryQuery = {},
|
query: HistoryQuery = {},
|
||||||
): Promise<PageResult<ImMessage>> {
|
): Promise<PageResult<ImMessage>> {
|
||||||
return HttpClient.get<PageResult<ImMessage>>(`/api/im/messages/history/${encodeURIComponent(toId)}`, {
|
const queryString = this.buildHistoryQuery(page, size, query)
|
||||||
appId: SDKContext.getConfig().appKey,
|
return HttpClient.get<PageResult<ImMessage>>('/api/im/messages/history/' + encodeURIComponent(toId), queryString)
|
||||||
page,
|
|
||||||
size,
|
|
||||||
msgType: query.msgType,
|
|
||||||
keyword: query.keyword,
|
|
||||||
startTime: query.startTime instanceof Date
|
|
||||||
? this.formatDateTime(query.startTime)
|
|
||||||
: query.startTime,
|
|
||||||
endTime: query.endTime instanceof Date
|
|
||||||
? this.formatDateTime(query.endTime)
|
|
||||||
: query.endTime,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchGroupHistory(
|
async fetchGroupHistory(
|
||||||
@ -130,55 +231,208 @@ export class ImClient {
|
|||||||
size: number = 50,
|
size: number = 50,
|
||||||
query: HistoryQuery = {},
|
query: HistoryQuery = {},
|
||||||
): Promise<PageResult<ImMessage>> {
|
): Promise<PageResult<ImMessage>> {
|
||||||
return HttpClient.get<PageResult<ImMessage>>(`/api/im/messages/group-history/${encodeURIComponent(groupId)}`, {
|
const queryString = this.buildHistoryQuery(page, size, query)
|
||||||
appId: SDKContext.getConfig().appKey,
|
return HttpClient.get<PageResult<ImMessage>>('/api/im/messages/group-history/' + encodeURIComponent(groupId), queryString)
|
||||||
page,
|
}
|
||||||
size,
|
|
||||||
msgType: query.msgType,
|
async locateHistoryPage(
|
||||||
keyword: query.keyword,
|
toId: string,
|
||||||
startTime: query.startTime instanceof Date
|
messageId: string,
|
||||||
? this.formatDateTime(query.startTime)
|
pageSize: number = 20,
|
||||||
: query.startTime,
|
maxPages: number = 20,
|
||||||
endTime: query.endTime instanceof Date
|
): Promise<ImMessage[] | null> {
|
||||||
? this.formatDateTime(query.endTime)
|
const pageCount = Math.max(maxPages, 1)
|
||||||
: query.endTime,
|
for (let page = 0; page < pageCount; page += 1) {
|
||||||
})
|
const result = await this.fetchHistory(toId, page, pageSize)
|
||||||
|
const messages = result.content ?? []
|
||||||
|
if (messages.some(item => item.id === messageId)) {
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
if (messages.length < pageSize) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async locateGroupHistoryPage(
|
||||||
|
groupId: string,
|
||||||
|
messageId: string,
|
||||||
|
pageSize: number = 50,
|
||||||
|
maxPages: number = 20,
|
||||||
|
): Promise<ImMessage[] | null> {
|
||||||
|
const pageCount = Math.max(maxPages, 1)
|
||||||
|
for (let page = 0; page < pageCount; page += 1) {
|
||||||
|
const result = await this.fetchGroupHistory(groupId, page, pageSize)
|
||||||
|
const messages = result.content ?? []
|
||||||
|
if (messages.some(item => item.id === messageId)) {
|
||||||
|
return messages
|
||||||
|
}
|
||||||
|
if (messages.length < pageSize) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async listConversations(size: number = 20): Promise<ConversationData[]> {
|
async listConversations(size: number = 20): Promise<ConversationData[]> {
|
||||||
return HttpClient.get<ConversationData[]>('/api/im/conversations', {
|
return HttpClient.get<ConversationData[]>('/api/im/conversations', this.buildConversationQuery(size))
|
||||||
appId: SDKContext.getConfig().appKey,
|
|
||||||
page: 0,
|
|
||||||
size,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async markRead(targetId: string, chatType: ChatType = 'SINGLE'): Promise<void> {
|
async markRead(targetId: string, chatType: ChatType = 'SINGLE'): Promise<void> {
|
||||||
await HttpClient.put<void>(`/api/im/conversations/${encodeURIComponent(targetId)}/read`, undefined, {
|
await HttpClient.put<void>('/api/im/conversations/' + encodeURIComponent(targetId) + '/read', undefined, this.buildConversationActionQuery(chatType))
|
||||||
appId: SDKContext.getConfig().appKey,
|
|
||||||
chatType,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async setDraft(targetId: string, chatType: ChatType, draft: string): Promise<void> {
|
async setDraft(targetId: string, chatType: ChatType, draft: string): Promise<void> {
|
||||||
await HttpClient.put<void>(`/api/im/conversations/${encodeURIComponent(targetId)}/draft`, undefined, {
|
const params = new DraftBody()
|
||||||
appId: SDKContext.getConfig().appKey,
|
params.appId = SDKContext.getConfig().appKey
|
||||||
chatType,
|
params.chatType = chatType
|
||||||
draft,
|
params.draft = draft
|
||||||
})
|
await HttpClient.put<void>('/api/im/conversations/' + encodeURIComponent(targetId) + '/draft', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async setConversationPinned(targetId: string, chatType: ChatType, pinned: boolean): Promise<void> {
|
||||||
|
const params = new PinBody()
|
||||||
|
params.appId = SDKContext.getConfig().appKey
|
||||||
|
params.chatType = chatType
|
||||||
|
params.pinned = pinned
|
||||||
|
await HttpClient.put<void>('/api/im/conversations/' + encodeURIComponent(targetId) + '/pinned', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async setConversationMuted(targetId: string, chatType: ChatType, muted: boolean): Promise<void> {
|
||||||
|
const params = new MuteBody()
|
||||||
|
params.appId = SDKContext.getConfig().appKey
|
||||||
|
params.chatType = chatType
|
||||||
|
params.muted = muted
|
||||||
|
await HttpClient.put<void>('/api/im/conversations/' + encodeURIComponent(targetId) + '/muted', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteConversation(targetId: string, chatType: ChatType): Promise<void> {
|
async deleteConversation(targetId: string, chatType: ChatType): Promise<void> {
|
||||||
await HttpClient.delete<void>(`/api/im/conversations/${encodeURIComponent(targetId)}`, {
|
await HttpClient.delete<void>('/api/im/conversations/' + encodeURIComponent(targetId), this.buildConversationActionQuery(chatType))
|
||||||
appId: SDKContext.getConfig().appKey,
|
}
|
||||||
chatType,
|
|
||||||
})
|
async listFriends(): Promise<string[]> {
|
||||||
|
return HttpClient.get<string[]>('/api/im/friends', this.buildAppQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
async listGroups(): Promise<ImGroup[]> {
|
||||||
|
return HttpClient.get<ImGroup[]>('/api/im/groups', this.buildAppQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroupInfo(groupId: string): Promise<ImGroup> {
|
||||||
|
return HttpClient.get<ImGroup>('/api/im/groups/' + encodeURIComponent(groupId), this.buildAppQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
async listGroupMembers(groupId: string): Promise<UserProfile[]> {
|
||||||
|
return HttpClient.get<UserProfile[]>(
|
||||||
|
'/api/im/groups/' + encodeURIComponent(groupId) + '/members',
|
||||||
|
this.buildAppQuery(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchGroupMembers(groupId: string, keyword: string, size: number = 20): Promise<UserProfile[]> {
|
||||||
|
return HttpClient.get<UserProfile[]>(
|
||||||
|
'/api/im/groups/' + encodeURIComponent(groupId) + '/members/search',
|
||||||
|
this.buildSearchQuery(keyword, size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchUsers(keyword: string, size: number = 20): Promise<UserProfile[]> {
|
||||||
|
return HttpClient.get<UserProfile[]>(
|
||||||
|
'/api/im/admin/users/search',
|
||||||
|
this.buildSearchQuery(keyword, size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchGroups(keyword: string, size: number = 20): Promise<ImGroup[]> {
|
||||||
|
return HttpClient.get<ImGroup[]>(
|
||||||
|
'/api/im/admin/groups/search',
|
||||||
|
this.buildSearchQuery(keyword, size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchMessages(
|
||||||
|
keyword: string = '',
|
||||||
|
chatType: ChatType | '' = '',
|
||||||
|
msgType: MsgType | '' = '',
|
||||||
|
page: number = 0,
|
||||||
|
size: number = 20,
|
||||||
|
): Promise<PageResult<ImMessage>> {
|
||||||
|
return HttpClient.get<PageResult<ImMessage>>(
|
||||||
|
'/api/im/admin/messages/search',
|
||||||
|
this.buildMessageSearchQuery(keyword, chatType, msgType, page, size),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async editMessage(messageId: string, content: string): Promise<ImMessage> {
|
||||||
|
const body = new EditMessageBody()
|
||||||
|
body.content = content
|
||||||
|
return HttpClient.put<ImMessage>(
|
||||||
|
'/api/im/messages/' + encodeURIComponent(messageId),
|
||||||
|
body,
|
||||||
|
this.buildAppQuery(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async revokeMessage(messageId: string): Promise<ImMessage> {
|
||||||
|
return HttpClient.post<ImMessage>(
|
||||||
|
'/api/im/messages/' + encodeURIComponent(messageId) + '/revoke',
|
||||||
|
this.buildAppBody(),
|
||||||
|
this.buildAppQuery(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendFriendRequest(toUserId: string, remark: string | null = null): Promise<FriendRequest> {
|
||||||
|
const params = new FriendRequestBody()
|
||||||
|
params.appId = SDKContext.getConfig().appKey
|
||||||
|
params.toUserId = toUserId
|
||||||
|
if (remark !== null && remark !== '') {
|
||||||
|
params.remark = remark
|
||||||
|
}
|
||||||
|
return HttpClient.post<FriendRequest>('/api/im/friend-requests', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async listFriendRequests(direction: 'incoming' | 'outgoing' = 'incoming'): Promise<FriendRequest[]> {
|
||||||
|
return HttpClient.get<FriendRequest[]>('/api/im/friend-requests', this.buildFriendRequestQuery(direction))
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptFriendRequest(requestId: string): Promise<FriendRequest> {
|
||||||
|
return HttpClient.post<FriendRequest>('/api/im/friend-requests/' + encodeURIComponent(requestId) + '/accept', this.buildAppBody())
|
||||||
|
}
|
||||||
|
|
||||||
|
async rejectFriendRequest(requestId: string): Promise<FriendRequest> {
|
||||||
|
return HttpClient.post<FriendRequest>('/api/im/friend-requests/' + encodeURIComponent(requestId) + '/reject', this.buildAppBody())
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendGroupJoinRequest(groupId: string, remark: string | null = null): Promise<GroupJoinRequest> {
|
||||||
|
const params = new GroupJoinRequestBody()
|
||||||
|
params.appId = SDKContext.getConfig().appKey
|
||||||
|
if (remark !== null && remark !== '') {
|
||||||
|
params.remark = remark
|
||||||
|
}
|
||||||
|
return HttpClient.post<GroupJoinRequest>('/api/im/groups/' + encodeURIComponent(groupId) + '/join-requests', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
async listGroupJoinRequests(groupId: string): Promise<GroupJoinRequest[]> {
|
||||||
|
return HttpClient.get<GroupJoinRequest[]>('/api/im/groups/' + encodeURIComponent(groupId) + '/join-requests', this.buildAppQuery())
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptGroupJoinRequest(groupId: string, requestId: string): Promise<GroupJoinRequest> {
|
||||||
|
return HttpClient.post<GroupJoinRequest>(
|
||||||
|
'/api/im/groups/' + encodeURIComponent(groupId) + '/join-requests/' + encodeURIComponent(requestId) + '/accept',
|
||||||
|
this.buildAppBody(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async rejectGroupJoinRequest(groupId: string, requestId: string): Promise<GroupJoinRequest> {
|
||||||
|
return HttpClient.post<GroupJoinRequest>(
|
||||||
|
'/api/im/groups/' + encodeURIComponent(groupId) + '/join-requests/' + encodeURIComponent(requestId) + '/reject',
|
||||||
|
this.buildAppBody(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProfile(userId: string): Promise<UserProfile> {
|
async getProfile(userId: string): Promise<UserProfile> {
|
||||||
return HttpClient.get<UserProfile>(`/api/im/accounts/${encodeURIComponent(userId)}`, {
|
return HttpClient.get<UserProfile>('/api/im/accounts/' + encodeURIComponent(userId), this.buildAppQuery())
|
||||||
appId: SDKContext.getConfig().appKey,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProfile(
|
async updateProfile(
|
||||||
@ -187,12 +441,91 @@ export class ImClient {
|
|||||||
avatar: string | null = null,
|
avatar: string | null = null,
|
||||||
gender: string | null = null,
|
gender: string | null = null,
|
||||||
): Promise<UserProfile> {
|
): Promise<UserProfile> {
|
||||||
return HttpClient.put<UserProfile>(`/api/im/accounts/${encodeURIComponent(userId)}`, undefined, {
|
const params = new UpdateProfileBody()
|
||||||
appId: SDKContext.getConfig().appKey,
|
params.appId = SDKContext.getConfig().appKey
|
||||||
...(nickname !== null ? { nickname } : {}),
|
if (nickname !== null) {
|
||||||
...(avatar !== null ? { avatar } : {}),
|
params.nickname = nickname
|
||||||
...(gender !== null ? { gender } : {}),
|
}
|
||||||
})
|
if (avatar !== null) {
|
||||||
|
params.avatar = avatar
|
||||||
|
}
|
||||||
|
if (gender !== null) {
|
||||||
|
params.gender = gender
|
||||||
|
}
|
||||||
|
return HttpClient.put<UserProfile>('/api/im/accounts/' + encodeURIComponent(userId), params)
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildAppBody(): AppBody {
|
||||||
|
const body = new AppBody()
|
||||||
|
body.appId = SDKContext.getConfig().appKey
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildAppQuery(): string {
|
||||||
|
return 'appId=' + encodeURIComponent(SDKContext.getConfig().appKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildConversationActionQuery(chatType: ChatType): string {
|
||||||
|
return 'appId=' + encodeURIComponent(SDKContext.getConfig().appKey) + '&chatType=' + encodeURIComponent(chatType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildConversationQuery(size: number): string {
|
||||||
|
return 'appId=' + encodeURIComponent(SDKContext.getConfig().appKey) + '&page=0&size=' + encodeURIComponent(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildFriendRequestQuery(direction: 'incoming' | 'outgoing'): string {
|
||||||
|
return 'appId=' + encodeURIComponent(SDKContext.getConfig().appKey) + '&direction=' + encodeURIComponent(direction)
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildSearchQuery(keyword: string, size: number): string {
|
||||||
|
return 'appId=' + encodeURIComponent(SDKContext.getConfig().appKey) +
|
||||||
|
'&keyword=' + encodeURIComponent(keyword) +
|
||||||
|
'&size=' + encodeURIComponent(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildMessageSearchQuery(
|
||||||
|
keyword: string,
|
||||||
|
chatType: ChatType | '',
|
||||||
|
msgType: MsgType | '',
|
||||||
|
page: number,
|
||||||
|
size: number,
|
||||||
|
): string {
|
||||||
|
const parts: string[] = []
|
||||||
|
parts.push('appId=' + encodeURIComponent(SDKContext.getConfig().appKey))
|
||||||
|
if (keyword !== '') {
|
||||||
|
parts.push('keyword=' + encodeURIComponent(keyword))
|
||||||
|
}
|
||||||
|
if (chatType !== '') {
|
||||||
|
parts.push('chatType=' + encodeURIComponent(chatType))
|
||||||
|
}
|
||||||
|
if (msgType !== '') {
|
||||||
|
parts.push('msgType=' + encodeURIComponent(msgType))
|
||||||
|
}
|
||||||
|
parts.push('page=' + encodeURIComponent(page))
|
||||||
|
parts.push('size=' + encodeURIComponent(size))
|
||||||
|
return parts.join('&')
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildHistoryQuery(page: number, size: number, query: HistoryQuery): string {
|
||||||
|
const parts: string[] = []
|
||||||
|
parts.push('appId=' + encodeURIComponent(SDKContext.getConfig().appKey))
|
||||||
|
parts.push('page=' + encodeURIComponent(page))
|
||||||
|
parts.push('size=' + encodeURIComponent(size))
|
||||||
|
if (query.msgType !== undefined) {
|
||||||
|
parts.push('msgType=' + encodeURIComponent(query.msgType))
|
||||||
|
}
|
||||||
|
if (query.keyword !== undefined && query.keyword !== '') {
|
||||||
|
parts.push('keyword=' + encodeURIComponent(query.keyword))
|
||||||
|
}
|
||||||
|
if (query.startTime !== undefined) {
|
||||||
|
const startValue = query.startTime instanceof Date ? this.formatDateTime(query.startTime) : String(query.startTime)
|
||||||
|
parts.push('startTime=' + encodeURIComponent(startValue))
|
||||||
|
}
|
||||||
|
if (query.endTime !== undefined) {
|
||||||
|
const endValue = query.endTime instanceof Date ? this.formatDateTime(query.endTime) : String(query.endTime)
|
||||||
|
parts.push('endTime=' + encodeURIComponent(endValue))
|
||||||
|
}
|
||||||
|
return parts.join('&')
|
||||||
}
|
}
|
||||||
|
|
||||||
disconnect(): void {
|
disconnect(): void {
|
||||||
@ -208,7 +541,7 @@ export class ImClient {
|
|||||||
private scheduleReconnect(): void {
|
private scheduleReconnect(): void {
|
||||||
if (this.destroyed) return
|
if (this.destroyed) return
|
||||||
const delay = this.reconnectDelay
|
const delay = this.reconnectDelay
|
||||||
if (SDKContext.getConfig().debug) console.log(`[ImClient] reconnect in ${delay}ms`)
|
if (SDKContext.getConfig().debug) console.log('[ImClient] reconnect in ' + delay + 'ms')
|
||||||
this.reconnectTimer = setTimeout(() => {
|
this.reconnectTimer = setTimeout(() => {
|
||||||
this.connect()
|
this.connect()
|
||||||
this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY)
|
this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY)
|
||||||
@ -219,7 +552,7 @@ export class ImClient {
|
|||||||
const messageId = params.messageId ?? this.generateMessageId()
|
const messageId = params.messageId ?? this.generateMessageId()
|
||||||
const userId = SDKContext.getUserId() ?? ''
|
const userId = SDKContext.getUserId() ?? ''
|
||||||
const appId = SDKContext.getConfig().appKey
|
const appId = SDKContext.getConfig().appKey
|
||||||
return {
|
const message: ImMessage = {
|
||||||
id: messageId,
|
id: messageId,
|
||||||
appId,
|
appId,
|
||||||
fromUserId: userId,
|
fromUserId: userId,
|
||||||
@ -234,25 +567,55 @@ export class ImClient {
|
|||||||
revoked: false,
|
revoked: false,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
}
|
}
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
|
||||||
|
private markFailed(message: ImMessage): ImMessage {
|
||||||
|
const failed: ImMessage = {
|
||||||
|
id: message.id,
|
||||||
|
appId: message.appId,
|
||||||
|
fromUserId: message.fromUserId,
|
||||||
|
fromId: message.fromId,
|
||||||
|
toId: message.toId,
|
||||||
|
chatType: message.chatType,
|
||||||
|
msgType: message.msgType,
|
||||||
|
content: message.content,
|
||||||
|
status: 'FAILED',
|
||||||
|
mentionedUserIds: message.mentionedUserIds,
|
||||||
|
groupReadCount: message.groupReadCount,
|
||||||
|
revoked: message.revoked,
|
||||||
|
createdAt: message.createdAt,
|
||||||
|
}
|
||||||
|
return failed
|
||||||
}
|
}
|
||||||
|
|
||||||
private normalizeMessage(message: ImMessage): ImMessage {
|
private normalizeMessage(message: ImMessage): ImMessage {
|
||||||
return {
|
const normalized: ImMessage = {
|
||||||
...message,
|
id: message.id,
|
||||||
fromId: message.fromId ?? message.fromUserId,
|
appId: message.appId || SDKContext.getConfig().appKey,
|
||||||
revoked: message.revoked ?? message.status === 'REVOKED',
|
fromUserId: message.fromUserId,
|
||||||
appId: message.appId ?? SDKContext.getConfig().appKey,
|
fromId: message.fromId || message.fromUserId,
|
||||||
|
toId: message.toId,
|
||||||
|
chatType: message.chatType,
|
||||||
|
msgType: message.msgType,
|
||||||
|
content: message.content,
|
||||||
|
status: message.status,
|
||||||
|
mentionedUserIds: message.mentionedUserIds,
|
||||||
|
groupReadCount: message.groupReadCount,
|
||||||
|
revoked: message.revoked || message.status === 'REVOKED',
|
||||||
|
createdAt: message.createdAt,
|
||||||
}
|
}
|
||||||
|
return normalized
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateMessageId(): string {
|
private generateMessageId(): string {
|
||||||
const cryptoId = globalThis.crypto?.randomUUID?.()
|
return 'msg_' + Date.now() + '_' + Math.floor(Math.random() * 1000000).toString(16)
|
||||||
if (cryptoId) return cryptoId
|
|
||||||
return `msg_${Date.now()}_${Math.random().toString(16).slice(2)}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatDateTime(value: Date): string {
|
private formatDateTime(value: Date): string {
|
||||||
const pad = (n: number) => String(n).padStart(2, '0')
|
const pad = (n: number): string => {
|
||||||
|
return n < 10 ? '0' + n : String(n)
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
value.getFullYear(),
|
value.getFullYear(),
|
||||||
'-',
|
'-',
|
||||||
@ -267,4 +630,14 @@ export class ImClient {
|
|||||||
pad(value.getSeconds()),
|
pad(value.getSeconds()),
|
||||||
].join('')
|
].join('')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private toStringValue(value: Date | string | number | undefined): string | undefined {
|
||||||
|
if (value === undefined) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (value instanceof Date) {
|
||||||
|
return this.formatDateTime(value)
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,17 @@
|
|||||||
import { HttpClient } from '../core/HttpClient'
|
import { HttpClient } from '../core/HttpClient'
|
||||||
import type { PushTokenInfo } from '../core/Types'
|
|
||||||
|
class PushRegisterBody {
|
||||||
|
vendor: string = 'HARMONY'
|
||||||
|
token: string = ''
|
||||||
|
platform: string = 'harmony'
|
||||||
|
imUserId: string | null = null
|
||||||
|
}
|
||||||
|
|
||||||
|
class PushUnregisterBody {
|
||||||
|
vendor: string = 'HARMONY'
|
||||||
|
token: string = ''
|
||||||
|
platform: string = 'harmony'
|
||||||
|
}
|
||||||
|
|
||||||
export class PushSDK {
|
export class PushSDK {
|
||||||
/**
|
/**
|
||||||
@ -8,21 +20,20 @@ export class PushSDK {
|
|||||||
* vendor should be 'HARMONY' for HarmonyOS devices.
|
* vendor should be 'HARMONY' for HarmonyOS devices.
|
||||||
*/
|
*/
|
||||||
static async registerToken(token: string, imUserId?: string): Promise<void> {
|
static async registerToken(token: string, imUserId?: string): Promise<void> {
|
||||||
const body: PushTokenInfo = {
|
const body = new PushRegisterBody()
|
||||||
vendor: 'HARMONY',
|
body.token = token
|
||||||
token,
|
if (imUserId !== undefined) {
|
||||||
platform: 'harmony',
|
body.imUserId = imUserId
|
||||||
}
|
}
|
||||||
await HttpClient.post<void>('/api/v1/push/register', {
|
await HttpClient.post<void>('/api/v1/push/register', body)
|
||||||
...body,
|
|
||||||
imUserId: imUserId ?? null,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregister push token on logout.
|
* Unregister push token on logout.
|
||||||
*/
|
*/
|
||||||
static async unregisterToken(token: string): Promise<void> {
|
static async unregisterToken(token: string): Promise<void> {
|
||||||
await HttpClient.post<void>('/api/v1/push/unregister', { token, platform: 'harmony' })
|
const body = new PushUnregisterBody()
|
||||||
|
body.token = token
|
||||||
|
await HttpClient.post<void>('/api/v1/push/unregister', body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"module": {
|
"module": {
|
||||||
"name": "xuqm-sdk",
|
"name": "xuqmSdk",
|
||||||
"type": "har",
|
"type": "har",
|
||||||
"deviceTypes": ["phone", "tablet"]
|
"deviceTypes": ["phone", "tablet"]
|
||||||
}
|
}
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户