feat: 鸿蒙 BugCollect 崩溃收集模块新建
新增模块:bugcollect/ - BugCollect.ets - 主入口,错误捕获 API - BugCollectTypes.ets - 数据模型 - Fingerprint.ets - 错误指纹计算(SHA-256) - LogQueue.ets - 内存队列 + 定时上传 功能: - captureError/captureCrash 捕获错误 - addBreadcrumb 添加面包屑 - event 记录自定义事件 - warn/info 日志级别记录 - 自动初始化(全局错误处理器) Co-Authored-By: Claude <noreply@anthropic.com>
这个提交包含在:
父节点
5a913ab4f0
当前提交
65e7a3e2c3
@ -6,6 +6,7 @@ export { SDKContext } from './src/main/ets/core/SDKContext'
|
|||||||
export { HttpClient } from './src/main/ets/core/HttpClient'
|
export { HttpClient } from './src/main/ets/core/HttpClient'
|
||||||
export { XWebViewComponent, XWebViewBuilder } from './src/main/ets/xwebview/XWebView'
|
export { XWebViewComponent, XWebViewBuilder } from './src/main/ets/xwebview/XWebView'
|
||||||
export { XWebViewBridge, openXWebView, getXWebViewControl, setXWebViewController, getXWebViewConfig } from './src/main/ets/xwebview/XWebViewBridge'
|
export { XWebViewBridge, openXWebView, getXWebViewControl, setXWebViewController, getXWebViewConfig } from './src/main/ets/xwebview/XWebViewBridge'
|
||||||
|
export { BugCollect } from './src/main/ets/bugcollect/BugCollect'
|
||||||
export type {
|
export type {
|
||||||
ConfigFile,
|
ConfigFile,
|
||||||
SDKConfig,
|
SDKConfig,
|
||||||
|
|||||||
@ -0,0 +1,261 @@
|
|||||||
|
/**
|
||||||
|
* BugCollect 崩溃收集 SDK
|
||||||
|
*/
|
||||||
|
|
||||||
|
import deviceInfo from '@ohos.deviceInfo'
|
||||||
|
import bundleManager from '@ohos.bundle.bundleManager'
|
||||||
|
import { SDKContext } from '../core/SDKContext'
|
||||||
|
import { computeFingerprint } from './Fingerprint'
|
||||||
|
import { LogQueue } from './LogQueue'
|
||||||
|
import type { IssueEvent, LogEvent, Breadcrumb, DeviceInfo, SdkInfo, LogLevel } from './BugCollectTypes'
|
||||||
|
|
||||||
|
const SDK_NAME = 'bugcollect.harmony'
|
||||||
|
const SDK_VERSION = '1.0.0'
|
||||||
|
const MAX_BREADCRUMBS = 50
|
||||||
|
|
||||||
|
export class BugCollect {
|
||||||
|
private static logLevel: LogLevel = 2 // WARN
|
||||||
|
private static environment: string = 'production'
|
||||||
|
private static queue: LogQueue | null = null
|
||||||
|
private static breadcrumbs: Breadcrumb[] = []
|
||||||
|
private static pendingErrors: IssueEvent[] = []
|
||||||
|
private static initialized = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置日志级别
|
||||||
|
*/
|
||||||
|
static setLogLevel(level: LogLevel): void {
|
||||||
|
BugCollect.logLevel = level
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置环境
|
||||||
|
*/
|
||||||
|
static setEnvironment(env: string): void {
|
||||||
|
BugCollect.environment = env
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加面包屑
|
||||||
|
*/
|
||||||
|
static addBreadcrumb(category: string, message: string, level: string = 'info', data?: Record<string, Object>): void {
|
||||||
|
if (BugCollect.breadcrumbs.length >= MAX_BREADCRUMBS) {
|
||||||
|
BugCollect.breadcrumbs.shift()
|
||||||
|
}
|
||||||
|
BugCollect.breadcrumbs.push({
|
||||||
|
timestamp: Date.now(),
|
||||||
|
category,
|
||||||
|
message,
|
||||||
|
level,
|
||||||
|
data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录事件
|
||||||
|
*/
|
||||||
|
static event(name: string, properties?: Record<string, Object>): void {
|
||||||
|
if (!BugCollect.initialized) return
|
||||||
|
const config = SDKContext.getConfig()
|
||||||
|
|
||||||
|
const logEvent: LogEvent = {
|
||||||
|
type: 'event',
|
||||||
|
eventId: BugCollect.generateUuid(),
|
||||||
|
name,
|
||||||
|
properties,
|
||||||
|
appKey: config.appKey,
|
||||||
|
userId: SDKContext.getUserId(),
|
||||||
|
platform: 'harmony',
|
||||||
|
release: BugCollect.getAppVersion(),
|
||||||
|
environment: BugCollect.environment,
|
||||||
|
device: BugCollect.buildDeviceInfo(),
|
||||||
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
||||||
|
}
|
||||||
|
|
||||||
|
BugCollect.queue?.push(logEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获错误
|
||||||
|
*/
|
||||||
|
static captureError(error: Error, tags?: Record<string, Object>, fingerprint?: string): void {
|
||||||
|
const issueEvent = BugCollect.buildIssueEvent('error', error, tags, fingerprint)
|
||||||
|
|
||||||
|
if (!BugCollect.initialized) {
|
||||||
|
BugCollect.pendingErrors.push(issueEvent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
BugCollect.flushPendingErrors()
|
||||||
|
BugCollect.queue?.push(issueEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 捕获致命错误
|
||||||
|
*/
|
||||||
|
static captureCrash(error: Error, tags?: Record<string, Object>): void {
|
||||||
|
if (!BugCollect.initialized) return
|
||||||
|
BugCollect.queue?.push(BugCollect.buildIssueEvent('fatal', error, tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录警告
|
||||||
|
*/
|
||||||
|
static warn(message: string, tags?: Record<string, Object>): void {
|
||||||
|
if (BugCollect.logLevel > 2) return
|
||||||
|
if (!BugCollect.initialized) return
|
||||||
|
|
||||||
|
const config = SDKContext.getConfig()
|
||||||
|
const issue: IssueEvent = {
|
||||||
|
type: 'issue',
|
||||||
|
eventId: BugCollect.generateUuid(),
|
||||||
|
level: 'warning',
|
||||||
|
platform: 'harmony',
|
||||||
|
fingerprint: '', // will be computed async
|
||||||
|
appKey: config.appKey,
|
||||||
|
exception: { type: 'Warning', value: message },
|
||||||
|
breadcrumbs: [...BugCollect.breadcrumbs],
|
||||||
|
release: BugCollect.getAppVersion(),
|
||||||
|
environment: BugCollect.environment,
|
||||||
|
userId: SDKContext.getUserId(),
|
||||||
|
user: { id: SDKContext.getUserId() ?? '' },
|
||||||
|
device: BugCollect.buildDeviceInfo(),
|
||||||
|
tags,
|
||||||
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fingerprint async
|
||||||
|
computeFingerprint('Warning', message, '').then(fp => {
|
||||||
|
issue.fingerprint = fp
|
||||||
|
BugCollect.queue?.push(issue)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录信息
|
||||||
|
*/
|
||||||
|
static info(message: string, tags?: Record<string, Object>): void {
|
||||||
|
if (BugCollect.logLevel > 1) return
|
||||||
|
BugCollect.event('__log_info', { ...tags, message })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动
|
||||||
|
*/
|
||||||
|
static start(): void {
|
||||||
|
if (BugCollect.initialized) return
|
||||||
|
BugCollect.initialized = true
|
||||||
|
|
||||||
|
const config = SDKContext.getConfig()
|
||||||
|
BugCollect.queue = new LogQueue(config.appKey, config.apiUrl)
|
||||||
|
|
||||||
|
// 注册崩溃处理器
|
||||||
|
BugCollect.registerCrashHandler()
|
||||||
|
|
||||||
|
// 刷新待处理的错误
|
||||||
|
BugCollect.flushPendingErrors()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 注册崩溃处理器
|
||||||
|
*/
|
||||||
|
private static registerCrashHandler(): void {
|
||||||
|
// HarmonyOS 错误处理
|
||||||
|
try {
|
||||||
|
// 注册全局错误处理器
|
||||||
|
ErrorUtils?.setGlobalHandler?.((error: Error) => {
|
||||||
|
BugCollect.captureCrash(error)
|
||||||
|
})
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新待处理的错误
|
||||||
|
*/
|
||||||
|
private static flushPendingErrors(): void {
|
||||||
|
if (BugCollect.pendingErrors.length === 0) return
|
||||||
|
const errors = [...BugCollect.pendingErrors]
|
||||||
|
BugCollect.pendingErrors = []
|
||||||
|
for (const error of errors) {
|
||||||
|
BugCollect.queue?.push(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建 IssueEvent
|
||||||
|
*/
|
||||||
|
private static buildIssueEvent(level: IssueEvent['level'], error: Error, tags?: Record<string, Object>, fingerprint?: string): IssueEvent {
|
||||||
|
const config = SDKContext.getConfig()
|
||||||
|
const stacktrace = error.stack ?? ''
|
||||||
|
|
||||||
|
const event: IssueEvent = {
|
||||||
|
type: 'issue',
|
||||||
|
eventId: BugCollect.generateUuid(),
|
||||||
|
level,
|
||||||
|
platform: 'harmony',
|
||||||
|
fingerprint: fingerprint ?? '', // will be computed async
|
||||||
|
appKey: config.appKey,
|
||||||
|
exception: {
|
||||||
|
type: error.name ?? 'Error',
|
||||||
|
value: error.message ?? '',
|
||||||
|
stacktrace,
|
||||||
|
},
|
||||||
|
breadcrumbs: [...BugCollect.breadcrumbs],
|
||||||
|
release: BugCollect.getAppVersion(),
|
||||||
|
environment: BugCollect.environment,
|
||||||
|
userId: SDKContext.getUserId(),
|
||||||
|
user: { id: SDKContext.getUserId() ?? '' },
|
||||||
|
device: BugCollect.buildDeviceInfo(),
|
||||||
|
tags,
|
||||||
|
sdk: { name: SDK_NAME, version: SDK_VERSION },
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute fingerprint async
|
||||||
|
if (!fingerprint) {
|
||||||
|
computeFingerprint(error.name ?? 'Error', error.message ?? '', stacktrace).then(fp => {
|
||||||
|
event.fingerprint = fp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return event
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建设备信息
|
||||||
|
*/
|
||||||
|
private static buildDeviceInfo(): DeviceInfo {
|
||||||
|
return {
|
||||||
|
model: deviceInfo.product ?? '',
|
||||||
|
manufacturer: deviceInfo.brand ?? '',
|
||||||
|
osName: 'HarmonyOS',
|
||||||
|
osVersion: deviceInfo.osReleaseType ?? '',
|
||||||
|
locale: deviceInfo.locale ?? '',
|
||||||
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取应用版本
|
||||||
|
*/
|
||||||
|
private static getAppVersion(): string {
|
||||||
|
try {
|
||||||
|
const bundleInfo = bundleManager.getBundleInfoForSelfSync(bundleManager.BundleFlag.GET_BUNDLE_INFO_DEFAULT)
|
||||||
|
return bundleInfo.versionName
|
||||||
|
} catch {
|
||||||
|
return '1.0.0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成 UUID
|
||||||
|
*/
|
||||||
|
private static generateUuid(): string {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
|
const r = Math.floor(Math.random() * 16)
|
||||||
|
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||||
|
return v.toString(16)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* BugCollect 类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// Issue 事件
|
||||||
|
export interface IssueEvent {
|
||||||
|
type: 'issue'
|
||||||
|
eventId: string
|
||||||
|
level: 'fatal' | 'error' | 'warning' | 'info' | 'debug'
|
||||||
|
platform: string
|
||||||
|
fingerprint: string
|
||||||
|
appKey: string
|
||||||
|
exception?: ExceptionInfo
|
||||||
|
breadcrumbs?: Breadcrumb[]
|
||||||
|
release: string
|
||||||
|
environment: string
|
||||||
|
userId?: string
|
||||||
|
user?: UserInfo
|
||||||
|
device?: DeviceInfo
|
||||||
|
tags?: Record<string, Object>
|
||||||
|
sdk?: SdkInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 异常信息
|
||||||
|
export interface ExceptionInfo {
|
||||||
|
type: string
|
||||||
|
value: string
|
||||||
|
stacktrace?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 用户信息
|
||||||
|
export interface UserInfo {
|
||||||
|
id: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设备信息
|
||||||
|
export interface DeviceInfo {
|
||||||
|
model?: string
|
||||||
|
manufacturer?: string
|
||||||
|
osName?: string
|
||||||
|
osVersion?: string
|
||||||
|
locale?: string
|
||||||
|
timezone?: string
|
||||||
|
isEmulator?: boolean
|
||||||
|
freeMemoryMb?: number
|
||||||
|
buildType?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SDK 信息
|
||||||
|
export interface SdkInfo {
|
||||||
|
name: string
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 面包屑
|
||||||
|
export interface Breadcrumb {
|
||||||
|
timestamp: number
|
||||||
|
category: string
|
||||||
|
message: string
|
||||||
|
level: string
|
||||||
|
data?: Record<string, Object>
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 日志事件
|
||||||
|
export interface LogEvent {
|
||||||
|
type: 'event'
|
||||||
|
eventId: string
|
||||||
|
name: string
|
||||||
|
properties?: Record<string, Object>
|
||||||
|
appKey: string
|
||||||
|
userId?: string
|
||||||
|
platform: string
|
||||||
|
release: string
|
||||||
|
environment: string
|
||||||
|
device?: DeviceInfo
|
||||||
|
sdk?: SdkInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 日志级别
|
||||||
|
export enum LogLevel {
|
||||||
|
DEBUG = 0,
|
||||||
|
INFO = 1,
|
||||||
|
WARN = 2,
|
||||||
|
ERROR = 3,
|
||||||
|
NONE = 4
|
||||||
|
}
|
||||||
@ -0,0 +1,59 @@
|
|||||||
|
/**
|
||||||
|
* 错误指纹计算
|
||||||
|
*/
|
||||||
|
|
||||||
|
import cryptoFramework from '@ohos.security.cryptoFramework'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算错误指纹
|
||||||
|
*/
|
||||||
|
export async function computeFingerprint(type: string, message: string, stacktrace?: string): Promise<string> {
|
||||||
|
const normalizedMessage = normalizeMessage(message)
|
||||||
|
const top3Frames = extractTop3Frames(stacktrace)
|
||||||
|
const raw = `${type}:${normalizedMessage}:${top3Frames}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
const md = cryptoFramework.createMd('SHA256')
|
||||||
|
const input = new Uint8Array(Buffer.from(raw, 'utf-8').buffer)
|
||||||
|
md.update({ data: input })
|
||||||
|
const output = md.digest()
|
||||||
|
const hash = Buffer.from(output.data).toString('hex')
|
||||||
|
return hash
|
||||||
|
} catch {
|
||||||
|
// Fallback: simple hash
|
||||||
|
return simpleHash(raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 归一化消息
|
||||||
|
*/
|
||||||
|
function normalizeMessage(message: string): string {
|
||||||
|
return message
|
||||||
|
.replace(/\d+/g, 'N')
|
||||||
|
.replace(/[0-9a-fA-F]{8,}/g, 'H')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提取堆栈顶部帧
|
||||||
|
*/
|
||||||
|
function extractTop3Frames(stacktrace?: string): string {
|
||||||
|
if (!stacktrace) return ''
|
||||||
|
return stacktrace
|
||||||
|
.split('\n')
|
||||||
|
.slice(0, 3)
|
||||||
|
.join('|')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 简单哈希(fallback)
|
||||||
|
*/
|
||||||
|
function simpleHash(str: string): string {
|
||||||
|
let hash = 0
|
||||||
|
for (let i = 0; i < str.length; i++) {
|
||||||
|
const char = str.charCodeAt(i)
|
||||||
|
hash = ((hash << 5) - hash) + char
|
||||||
|
hash = hash & hash
|
||||||
|
}
|
||||||
|
return Math.abs(hash).toString(16)
|
||||||
|
}
|
||||||
@ -0,0 +1,141 @@
|
|||||||
|
/**
|
||||||
|
* 日志队列
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { HttpClient } from '../core/HttpClient'
|
||||||
|
import { SDKContext } from '../core/SDKContext'
|
||||||
|
import type { IssueEvent, LogEvent } from './BugCollectTypes'
|
||||||
|
|
||||||
|
const BATCH_SIZE = 30
|
||||||
|
const FLUSH_INTERVAL_MS = 10000
|
||||||
|
const MAX_QUEUE_SIZE = 500
|
||||||
|
const MAX_RETRY = 3
|
||||||
|
const STORAGE_KEY = 'xuqm_bugcollect_queue'
|
||||||
|
|
||||||
|
type Event = IssueEvent | LogEvent
|
||||||
|
|
||||||
|
export class LogQueue {
|
||||||
|
private memory: Event[] = []
|
||||||
|
private flushTimer: number = 0
|
||||||
|
private retryCount = 0
|
||||||
|
private appKey: string
|
||||||
|
private baseUrl: string
|
||||||
|
|
||||||
|
constructor(appKey: string, baseUrl: string) {
|
||||||
|
this.appKey = appKey
|
||||||
|
this.baseUrl = baseUrl
|
||||||
|
this.flushTimer = setInterval(() => this.flush(), FLUSH_INTERVAL_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 推送事件
|
||||||
|
*/
|
||||||
|
async push(event: Event): Promise<void> {
|
||||||
|
if (this.shouldPersist(event)) {
|
||||||
|
const stored = await this.readStored()
|
||||||
|
if (stored.length >= MAX_QUEUE_SIZE) stored.shift()
|
||||||
|
stored.push(event)
|
||||||
|
await this.writeStored(stored)
|
||||||
|
} else {
|
||||||
|
if (this.memory.length >= MAX_QUEUE_SIZE) this.memory.shift()
|
||||||
|
this.memory.push(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新队列
|
||||||
|
*/
|
||||||
|
async flush(): Promise<void> {
|
||||||
|
const stored = await this.readStored()
|
||||||
|
const combined = [...stored, ...this.memory]
|
||||||
|
if (combined.length === 0) return
|
||||||
|
|
||||||
|
const batch = combined.slice(0, BATCH_SIZE)
|
||||||
|
const storedUsed = Math.min(stored.length, BATCH_SIZE)
|
||||||
|
const memUsed = batch.length - storedUsed
|
||||||
|
|
||||||
|
// 乐观移除
|
||||||
|
await this.writeStored(stored.slice(storedUsed))
|
||||||
|
this.memory.splice(0, memUsed)
|
||||||
|
|
||||||
|
const issues = batch.filter((e): e is IssueEvent => e.type === 'issue')
|
||||||
|
const events = batch.filter((e): e is LogEvent => e.type === 'event')
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (issues.length > 0) {
|
||||||
|
await this.post('/bugcollect/v1/issues/batch', issues)
|
||||||
|
}
|
||||||
|
if (events.length > 0) {
|
||||||
|
await this.post('/bugcollect/v1/events/batch', events)
|
||||||
|
}
|
||||||
|
this.retryCount = 0
|
||||||
|
} catch {
|
||||||
|
this.retryCount++
|
||||||
|
if (this.retryCount < MAX_RETRY) {
|
||||||
|
const toRequeue = batch.filter(e => this.shouldPersist(e))
|
||||||
|
if (toRequeue.length > 0) {
|
||||||
|
const current = await this.readStored()
|
||||||
|
await this.writeStored([...toRequeue, ...current])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁队列
|
||||||
|
*/
|
||||||
|
destroy(): void {
|
||||||
|
if (this.flushTimer) {
|
||||||
|
clearInterval(this.flushTimer)
|
||||||
|
this.flushTimer = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否需要持久化
|
||||||
|
*/
|
||||||
|
private shouldPersist(event: Event): boolean {
|
||||||
|
return event.type === 'issue' && (event.level === 'fatal' || event.level === 'error')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传数据
|
||||||
|
*/
|
||||||
|
private async post(path: string, data: Event[]): Promise<void> {
|
||||||
|
const body = JSON.stringify({
|
||||||
|
sentAt: new Date().toISOString(),
|
||||||
|
sdk: { name: 'bugcollect.harmony', version: '1.0.0' },
|
||||||
|
events: data,
|
||||||
|
})
|
||||||
|
|
||||||
|
await HttpClient.post(path, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 读取持久化队列
|
||||||
|
*/
|
||||||
|
private async readStored(): Promise<Event[]> {
|
||||||
|
try {
|
||||||
|
const raw = await SDKContext.getPreference(STORAGE_KEY)
|
||||||
|
if (!raw) return []
|
||||||
|
return JSON.parse(raw) as Event[]
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 写入持久化队列
|
||||||
|
*/
|
||||||
|
private async writeStored(queue: Event[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (queue.length === 0) {
|
||||||
|
await SDKContext.setPreference(STORAGE_KEY, '')
|
||||||
|
} else {
|
||||||
|
await SDKContext.setPreference(STORAGE_KEY, JSON.stringify(queue))
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
正在加载...
在新工单中引用
屏蔽一个用户