@hd-front-end/jsbridge-sdk 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/index.d.ts +204 -2
- package/dist/index.esm.js +446 -1
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +448 -0
- package/dist/index.js.map +1 -1
- package/dist/index.umd.js +450 -4
- package/dist/index.umd.js.map +1 -1
- package/docs/00-/351/241/271/347/233/256/346/246/202/350/247/210.md +282 -0
- package/docs/01-/346/236/266/346/236/204/350/256/276/350/256/241.md +623 -0
- package/docs/02-/346/212/200/346/234/257/345/256/236/347/216/260.md +867 -0
- package/docs/03-API/344/275/277/347/224/250/346/226/207/346/241/243.md +1104 -0
- package/docs/04-/346/265/213/350/257/225/346/226/271/346/241/210.md +360 -0
- package/docs/05-/350/277/201/347/247/273/346/214/207/345/215/227.md +181 -0
- package/docs/06-/346/236/266/346/236/204/345/233/276/351/233/206.md +738 -0
- package/docs/07-/346/226/260/346/241/245/346/216/245/346/226/271/346/263/225/346/211/251/345/261/225/350/257/264/346/230/216.md +139 -0
- package/docs/CODE_REVIEW.md +65 -0
- package/docs/EVALUATION.md +72 -0
- package/docs/README.md +258 -0
- package/docs//345/205/263/351/224/256/351/227/256/351/242/230/350/247/243/347/255/224.md +495 -0
- package/docs//346/226/207/346/241/243/346/225/264/345/220/210/350/257/264/346/230/216.md +265 -0
- package/docs//346/233/264/346/226/260/346/227/245/345/277/227.md +669 -0
- package/docs//347/224/237/344/272/247/347/272/247-/345/277/253/351/200/237/345/274/200/345/247/213-v2.md +673 -0
- package/docs//347/224/237/344/272/247/347/272/247-/346/236/266/346/236/204/350/256/276/350/256/241-v2.md +730 -0
- package/docs//350/256/276/350/256/241/347/220/206/345/277/265/350/257/264/346/230/216.md +438 -0
- package/package.json +3 -2
|
@@ -0,0 +1,867 @@
|
|
|
1
|
+
# JSBridge SDK 技术实现(生产级 v2)
|
|
2
|
+
|
|
3
|
+
> **完整代码实现(代码级文档)**
|
|
4
|
+
|
|
5
|
+
## 📁 目录结构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── core/
|
|
10
|
+
│ └── Bridge.ts # 200 行
|
|
11
|
+
├── monitor/
|
|
12
|
+
│ └── Monitor.ts # 180 行
|
|
13
|
+
├── debug/
|
|
14
|
+
│ └── Debug.ts # 150 行
|
|
15
|
+
├── api/
|
|
16
|
+
│ ├── device.ts # 80 行
|
|
17
|
+
│ ├── media.ts # 90 行
|
|
18
|
+
│ ├── router.ts # 60 行
|
|
19
|
+
│ ├── storage.ts # 50 行
|
|
20
|
+
│ ├── system.ts # 40 行
|
|
21
|
+
│ └── log.ts # 80 行 ← 新增
|
|
22
|
+
├── types/
|
|
23
|
+
│ └── index.ts # 120 行
|
|
24
|
+
└── index.ts # 100 行
|
|
25
|
+
|
|
26
|
+
总计:~1150 行
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 1. Bridge.ts - 核心通信(200行)
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// src/core/Bridge.ts
|
|
33
|
+
/**
|
|
34
|
+
* 平台类型
|
|
35
|
+
*/
|
|
36
|
+
export enum Platform {
|
|
37
|
+
Android = 'android',
|
|
38
|
+
iOS = 'ios',
|
|
39
|
+
Unknown = 'unknown'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* JSBridge 核心通信类
|
|
44
|
+
* 兼容 Android 和 iOS
|
|
45
|
+
*/
|
|
46
|
+
class Bridge {
|
|
47
|
+
private bridge: any = null
|
|
48
|
+
private isInitialized: boolean = false
|
|
49
|
+
private platform: Platform = Platform.Unknown
|
|
50
|
+
|
|
51
|
+
constructor() {
|
|
52
|
+
this.detectPlatform()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 检测平台
|
|
57
|
+
*/
|
|
58
|
+
private detectPlatform(): void {
|
|
59
|
+
const ua = navigator.userAgent
|
|
60
|
+
|
|
61
|
+
if (/SoaAndroid|HDApp.*Android/i.test(ua)) {
|
|
62
|
+
this.platform = Platform.Android
|
|
63
|
+
console.log('[JSBridge] 检测到 Android 平台')
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (/iPhone|iPad|iPod/i.test(ua) && /HDApp/i.test(ua)) {
|
|
68
|
+
this.platform = Platform.iOS
|
|
69
|
+
console.log('[JSBridge] 检测到 iOS 平台')
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
this.platform = Platform.Unknown
|
|
74
|
+
console.log('[JSBridge] 未检测到原生环境')
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 初始化 JSBridge
|
|
79
|
+
*/
|
|
80
|
+
async init(): Promise<boolean> {
|
|
81
|
+
if (this.isInitialized) return true
|
|
82
|
+
if (this.platform === Platform.Unknown) {
|
|
83
|
+
console.warn('[JSBridge] 不在原生 App 环境中')
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
// 检查是否已有 Bridge 实例
|
|
89
|
+
if (window.WKWebViewJavascriptBridge) {
|
|
90
|
+
this.bridge = window.WKWebViewJavascriptBridge
|
|
91
|
+
this.isInitialized = true
|
|
92
|
+
console.log(`[JSBridge] ${this.platform} 初始化成功(已存在)`)
|
|
93
|
+
resolve(true)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 等待 Bridge 注入
|
|
98
|
+
window.WKWVJBCallbacks = window.WKWVJBCallbacks || []
|
|
99
|
+
window.WKWVJBCallbacks.push((bridge: any) => {
|
|
100
|
+
this.bridge = bridge
|
|
101
|
+
this.isInitialized = true
|
|
102
|
+
console.log(`[JSBridge] ${this.platform} 初始化成功`)
|
|
103
|
+
resolve(true)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Android 特定初始化
|
|
107
|
+
if (this.platform === Platform.Android) {
|
|
108
|
+
if (window.InjectJavascript) {
|
|
109
|
+
window.InjectJavascript.init()
|
|
110
|
+
console.log('[JSBridge] Android 触发初始化')
|
|
111
|
+
} else {
|
|
112
|
+
console.error('[JSBridge] Android InjectJavascript 不存在')
|
|
113
|
+
reject(new Error('Android InjectJavascript 不存在'))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// iOS 特定初始化
|
|
118
|
+
if (this.platform === Platform.iOS) {
|
|
119
|
+
const iframe = document.createElement('iframe')
|
|
120
|
+
iframe.style.display = 'none'
|
|
121
|
+
iframe.src = 'https://__bridge_loaded__'
|
|
122
|
+
document.documentElement.appendChild(iframe)
|
|
123
|
+
setTimeout(() => document.documentElement.removeChild(iframe), 0)
|
|
124
|
+
console.log('[JSBridge] iOS 触发初始化')
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 超时处理(10 秒)
|
|
128
|
+
setTimeout(() => {
|
|
129
|
+
if (!this.isInitialized) {
|
|
130
|
+
console.error(`[JSBridge] ${this.platform} 初始化超时`)
|
|
131
|
+
reject(new Error(`${this.platform} 初始化超时`))
|
|
132
|
+
}
|
|
133
|
+
}, 10000)
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 调用原生方法
|
|
139
|
+
*/
|
|
140
|
+
call<T = any>(method: string, data?: any): Promise<T> {
|
|
141
|
+
if (this.platform === Platform.Unknown) {
|
|
142
|
+
return Promise.reject(new Error('不在原生 App 环境中'))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!this.isInitialized) {
|
|
146
|
+
return Promise.reject(new Error('JSBridge 未初始化'))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(`[JSBridge] [${this.platform}] 调用: ${method}`, data)
|
|
150
|
+
|
|
151
|
+
return new Promise((resolve, reject) => {
|
|
152
|
+
try {
|
|
153
|
+
this.bridge.callHandler(method, data, (result: any) => {
|
|
154
|
+
console.log(`[JSBridge] [${this.platform}] 返回: ${method}`, result)
|
|
155
|
+
resolve(result)
|
|
156
|
+
})
|
|
157
|
+
} catch (error) {
|
|
158
|
+
console.error(`[JSBridge] [${this.platform}] 调用失败: ${method}`, error)
|
|
159
|
+
reject(error)
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 注册方法供原生调用
|
|
166
|
+
*/
|
|
167
|
+
register(method: string, handler: (data: any, callback: any) => void): void {
|
|
168
|
+
if (this.platform === Platform.Unknown || !this.isInitialized) {
|
|
169
|
+
console.warn(`[JSBridge] 无法注册方法: ${method}`)
|
|
170
|
+
return
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
console.log(`[JSBridge] [${this.platform}] 注册方法: ${method}`)
|
|
174
|
+
|
|
175
|
+
this.bridge.registerHandler(method, (data: any, responseCallback: any) => {
|
|
176
|
+
console.log(`[JSBridge] [${this.platform}] 原生调用: ${method}`, data)
|
|
177
|
+
try {
|
|
178
|
+
handler(data, responseCallback)
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(`[JSBridge] [${this.platform}] 处理失败: ${method}`, error)
|
|
181
|
+
responseCallback?.({ error: (error as Error).message })
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
getPlatform(): Platform {
|
|
187
|
+
return this.platform
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
inApp(): boolean {
|
|
191
|
+
return this.platform !== Platform.Unknown
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
isReady(): boolean {
|
|
195
|
+
return this.isInitialized
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export const bridge = new Bridge()
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## 2. log.ts - 日志API(80行)← 新增
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
// src/api/log.ts
|
|
206
|
+
import { bridge } from '../core/Bridge'
|
|
207
|
+
import { monitor } from '../monitor/Monitor'
|
|
208
|
+
import { debug } from '../debug/Debug'
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 日志级别
|
|
212
|
+
*/
|
|
213
|
+
export enum LogLevel {
|
|
214
|
+
DEBUG = 'DEBUG',
|
|
215
|
+
INFO = 'INFO',
|
|
216
|
+
WARN = 'WARN',
|
|
217
|
+
ERROR = 'ERROR'
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 日志请求参数
|
|
222
|
+
*/
|
|
223
|
+
export interface LogRequest {
|
|
224
|
+
tag?: string
|
|
225
|
+
level: LogLevel
|
|
226
|
+
message: string
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* 写日志到原生
|
|
231
|
+
*/
|
|
232
|
+
export async function writeLog(request: LogRequest): Promise<void> {
|
|
233
|
+
const record = monitor.recordStart('log', bridge.getPlatform(), request)
|
|
234
|
+
|
|
235
|
+
try {
|
|
236
|
+
await bridge.call('log', {
|
|
237
|
+
tag: request.tag || 'JSBridge',
|
|
238
|
+
level: request.level,
|
|
239
|
+
message: request.message
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
monitor.recordEnd(record, true)
|
|
243
|
+
debug.log('debug', `日志已写入: [${request.level}] ${request.message}`)
|
|
244
|
+
} catch (error) {
|
|
245
|
+
monitor.recordEnd(record, false, undefined, (error as Error).message)
|
|
246
|
+
debug.log('error', `写入日志失败: ${(error as Error).message}`)
|
|
247
|
+
throw error
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* 便捷方法
|
|
253
|
+
*/
|
|
254
|
+
export const log = {
|
|
255
|
+
debug: (message: string, tag?: string) =>
|
|
256
|
+
writeLog({ level: LogLevel.DEBUG, message, tag }),
|
|
257
|
+
|
|
258
|
+
info: (message: string, tag?: string) =>
|
|
259
|
+
writeLog({ level: LogLevel.INFO, message, tag }),
|
|
260
|
+
|
|
261
|
+
warn: (message: string, tag?: string) =>
|
|
262
|
+
writeLog({ level: LogLevel.WARN, message, tag }),
|
|
263
|
+
|
|
264
|
+
error: (message: string, tag?: string) =>
|
|
265
|
+
writeLog({ level: LogLevel.ERROR, message, tag })
|
|
266
|
+
}
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## 3. Monitor.ts - 监控(180行)
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// src/monitor/Monitor.ts
|
|
273
|
+
import { bridge } from '../core/Bridge'
|
|
274
|
+
|
|
275
|
+
export interface MonitorConfig {
|
|
276
|
+
enabled: boolean
|
|
277
|
+
autoReport?: boolean
|
|
278
|
+
reportInterval?: number
|
|
279
|
+
maxCacheSize?: number
|
|
280
|
+
logTag?: string
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export interface ApiCallRecord {
|
|
284
|
+
api: string
|
|
285
|
+
platform: string
|
|
286
|
+
startTime: number
|
|
287
|
+
endTime?: number
|
|
288
|
+
duration?: number
|
|
289
|
+
success: boolean
|
|
290
|
+
error?: string
|
|
291
|
+
params?: any
|
|
292
|
+
result?: any
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export interface PerformanceStats {
|
|
296
|
+
totalCalls: number
|
|
297
|
+
successCalls: number
|
|
298
|
+
failedCalls: number
|
|
299
|
+
avgDuration: number
|
|
300
|
+
maxDuration: number
|
|
301
|
+
minDuration: number
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
class Monitor {
|
|
305
|
+
private config: MonitorConfig
|
|
306
|
+
private records: ApiCallRecord[] = []
|
|
307
|
+
private stats: Map<string, PerformanceStats> = new Map()
|
|
308
|
+
private reportTimer?: NodeJS.Timeout
|
|
309
|
+
|
|
310
|
+
constructor(config: Partial<MonitorConfig> = {}) {
|
|
311
|
+
this.config = {
|
|
312
|
+
enabled: true,
|
|
313
|
+
reportInterval: 60000,
|
|
314
|
+
maxCacheSize: 100,
|
|
315
|
+
autoReport: false,
|
|
316
|
+
logTag: 'JSBridge-Monitor',
|
|
317
|
+
...config
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
if (this.config.autoReport) {
|
|
321
|
+
this.startAutoReport()
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
recordStart(api: string, platform: string, params?: any): ApiCallRecord {
|
|
326
|
+
if (!this.config.enabled) {
|
|
327
|
+
return {} as ApiCallRecord
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
api,
|
|
332
|
+
platform,
|
|
333
|
+
startTime: Date.now(),
|
|
334
|
+
success: false,
|
|
335
|
+
params
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
recordEnd(record: ApiCallRecord, success: boolean, result?: any, error?: string): void {
|
|
340
|
+
if (!this.config.enabled) {
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
record.endTime = Date.now()
|
|
345
|
+
record.duration = record.endTime - record.startTime
|
|
346
|
+
record.success = success
|
|
347
|
+
record.result = result
|
|
348
|
+
record.error = error
|
|
349
|
+
|
|
350
|
+
this.records.push(record)
|
|
351
|
+
|
|
352
|
+
if (this.records.length > this.config.maxCacheSize!) {
|
|
353
|
+
this.records.shift()
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
this.updateStats(record)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
private updateStats(record: ApiCallRecord): void {
|
|
360
|
+
if (!record.api) return
|
|
361
|
+
|
|
362
|
+
let stats = this.stats.get(record.api)
|
|
363
|
+
if (!stats) {
|
|
364
|
+
stats = {
|
|
365
|
+
totalCalls: 0,
|
|
366
|
+
successCalls: 0,
|
|
367
|
+
failedCalls: 0,
|
|
368
|
+
avgDuration: 0,
|
|
369
|
+
maxDuration: 0,
|
|
370
|
+
minDuration: Infinity
|
|
371
|
+
}
|
|
372
|
+
this.stats.set(record.api, stats)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
stats.totalCalls++
|
|
376
|
+
if (record.success) {
|
|
377
|
+
stats.successCalls++
|
|
378
|
+
} else {
|
|
379
|
+
stats.failedCalls++
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if (record.duration) {
|
|
383
|
+
stats.maxDuration = Math.max(stats.maxDuration, record.duration)
|
|
384
|
+
stats.minDuration = Math.min(stats.minDuration, record.duration)
|
|
385
|
+
stats.avgDuration = ((stats.avgDuration * (stats.totalCalls - 1)) + record.duration) / stats.totalCalls
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* 上报监控数据
|
|
391
|
+
* ✅ v2: 通过原生 log 能力上报
|
|
392
|
+
*/
|
|
393
|
+
async report(): Promise<void> {
|
|
394
|
+
if (this.records.length === 0) {
|
|
395
|
+
return
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const data = {
|
|
400
|
+
records: this.records,
|
|
401
|
+
stats: Array.from(this.stats.entries()).map(([api, stats]) => ({
|
|
402
|
+
api,
|
|
403
|
+
...stats
|
|
404
|
+
})),
|
|
405
|
+
timestamp: Date.now()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// ✅ 通过原生 log 能力上报
|
|
409
|
+
await bridge.call('log', {
|
|
410
|
+
tag: this.config.logTag,
|
|
411
|
+
level: 'INFO',
|
|
412
|
+
message: `[MONITOR] ${JSON.stringify(data)}`
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
console.log('[Monitor] 监控数据已上报到原生日志')
|
|
416
|
+
this.clear()
|
|
417
|
+
} catch (error) {
|
|
418
|
+
console.error('[Monitor] 上报失败', error)
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* 立即上报错误
|
|
424
|
+
*/
|
|
425
|
+
async reportError(api: string, error: string, detail?: any): Promise<void> {
|
|
426
|
+
if (!this.config.enabled) {
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
try {
|
|
431
|
+
const errorData = {
|
|
432
|
+
type: 'error',
|
|
433
|
+
api,
|
|
434
|
+
error,
|
|
435
|
+
detail,
|
|
436
|
+
platform: bridge.getPlatform(),
|
|
437
|
+
timestamp: Date.now()
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ✅ 立即通过原生 log 能力上报错误
|
|
441
|
+
await bridge.call('log', {
|
|
442
|
+
tag: this.config.logTag,
|
|
443
|
+
level: 'ERROR',
|
|
444
|
+
message: `[ERROR] ${JSON.stringify(errorData)}`
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
console.error('[Monitor] 错误已上报:', errorData)
|
|
448
|
+
} catch (e) {
|
|
449
|
+
console.error('[Monitor] 上报错误失败', e)
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
private startAutoReport(): void {
|
|
454
|
+
this.reportTimer = setInterval(() => {
|
|
455
|
+
this.report()
|
|
456
|
+
}, this.config.reportInterval)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
updateConfig(config: Partial<MonitorConfig>): void {
|
|
460
|
+
this.config = { ...this.config, ...config }
|
|
461
|
+
|
|
462
|
+
if (this.config.autoReport && !this.reportTimer) {
|
|
463
|
+
this.startAutoReport()
|
|
464
|
+
} else if (!this.config.autoReport && this.reportTimer) {
|
|
465
|
+
clearInterval(this.reportTimer)
|
|
466
|
+
this.reportTimer = undefined
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
getStats(api?: string): any {
|
|
471
|
+
if (api) {
|
|
472
|
+
return this.stats.get(api) || null
|
|
473
|
+
}
|
|
474
|
+
return this.stats
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
getRecords(limit?: number): ApiCallRecord[] {
|
|
478
|
+
if (limit) {
|
|
479
|
+
return this.records.slice(-limit)
|
|
480
|
+
}
|
|
481
|
+
return [...this.records]
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
clear(): void {
|
|
485
|
+
this.records = []
|
|
486
|
+
this.stats.clear()
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
export const monitor = new Monitor()
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
## 4. index.ts - 统一导出(100行)
|
|
494
|
+
|
|
495
|
+
```typescript
|
|
496
|
+
// src/index.ts
|
|
497
|
+
import { bridge, Platform } from './core/Bridge'
|
|
498
|
+
import { monitor, MonitorConfig } from './monitor/Monitor'
|
|
499
|
+
import { debug, DebugConfig } from './debug/Debug'
|
|
500
|
+
import * as device from './api/device'
|
|
501
|
+
import * as media from './api/media'
|
|
502
|
+
import * as router from './api/router'
|
|
503
|
+
import * as storage from './api/storage'
|
|
504
|
+
import * as system from './api/system'
|
|
505
|
+
import { writeLog, log, LogLevel, LogRequest } from './api/log' // ← 新增
|
|
506
|
+
|
|
507
|
+
export interface JSBridgeConfig {
|
|
508
|
+
debug?: Partial<DebugConfig>
|
|
509
|
+
monitor?: Partial<MonitorConfig>
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 初始化 JSBridge
|
|
514
|
+
*/
|
|
515
|
+
export async function init(config?: JSBridgeConfig): Promise<boolean> {
|
|
516
|
+
try {
|
|
517
|
+
// 配置 debug
|
|
518
|
+
if (config?.debug) {
|
|
519
|
+
debug.updateConfig(config.debug)
|
|
520
|
+
if (config.debug.useVConsole) {
|
|
521
|
+
await debug.initVConsole()
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// 配置 monitor
|
|
526
|
+
if (config?.monitor) {
|
|
527
|
+
monitor.updateConfig(config.monitor)
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// 初始化 bridge
|
|
531
|
+
const success = await bridge.init()
|
|
532
|
+
|
|
533
|
+
// 保存平台信息
|
|
534
|
+
(window as any).__jsbridgePlatform = bridge.getPlatform()
|
|
535
|
+
|
|
536
|
+
// ✅ 通过 log API 记录初始化成功
|
|
537
|
+
if (success) {
|
|
538
|
+
await writeLog({
|
|
539
|
+
level: LogLevel.INFO,
|
|
540
|
+
message: `JSBridge 初始化成功 [${bridge.getPlatform()}]`
|
|
541
|
+
})
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
return success
|
|
545
|
+
} catch (error) {
|
|
546
|
+
console.error('[JSBridge] 初始化失败', error)
|
|
547
|
+
debug.log('error', '初始化失败', error)
|
|
548
|
+
|
|
549
|
+
// 尝试上报错误
|
|
550
|
+
try {
|
|
551
|
+
await writeLog({
|
|
552
|
+
level: LogLevel.ERROR,
|
|
553
|
+
message: `JSBridge 初始化失败: ${(error as Error).message}`
|
|
554
|
+
})
|
|
555
|
+
} catch (e) {
|
|
556
|
+
// 忽略上报失败
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return false
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function inApp(): boolean {
|
|
564
|
+
return bridge.inApp()
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
export function getPlatform(): Platform {
|
|
568
|
+
return bridge.getPlatform()
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* JSBridge API
|
|
573
|
+
*/
|
|
574
|
+
export const JSBridge = {
|
|
575
|
+
// 设备相关
|
|
576
|
+
scan: device.scan,
|
|
577
|
+
vibrate: device.vibrate,
|
|
578
|
+
getDeviceInfo: device.getDeviceInfo,
|
|
579
|
+
getNetworkType: device.getNetworkType,
|
|
580
|
+
|
|
581
|
+
// 媒体相关
|
|
582
|
+
chooseMedia: media.chooseMedia,
|
|
583
|
+
uploadFile: media.uploadFile,
|
|
584
|
+
previewImage: media.previewImage,
|
|
585
|
+
getImageInfo: media.getImageInfo,
|
|
586
|
+
bluetoothPrint: media.bluetoothPrint,
|
|
587
|
+
|
|
588
|
+
// 路由相关
|
|
589
|
+
setNavigationBar: router.setNavigationBar,
|
|
590
|
+
closeWebView: router.closeWebView,
|
|
591
|
+
onRoute: router.onRoute,
|
|
592
|
+
onGetRouteInfo: router.onGetRouteInfo,
|
|
593
|
+
notifyRouteChange: router.notifyRouteChange,
|
|
594
|
+
|
|
595
|
+
// 存储相关
|
|
596
|
+
getStorage: storage.getStorage,
|
|
597
|
+
setStorage: storage.setStorage,
|
|
598
|
+
getAppInfo: storage.getAppInfo,
|
|
599
|
+
onRefreshStore: storage.onRefreshStore,
|
|
600
|
+
notifyAppOnload: storage.notifyAppOnload,
|
|
601
|
+
|
|
602
|
+
// 系统相关
|
|
603
|
+
makePhoneCall: system.makePhoneCall,
|
|
604
|
+
onPdaScan: system.onPdaScan,
|
|
605
|
+
|
|
606
|
+
// ← 日志相关(新增)
|
|
607
|
+
writeLog,
|
|
608
|
+
log
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* 监控 API
|
|
613
|
+
*/
|
|
614
|
+
export const Monitor = {
|
|
615
|
+
getStats: monitor.getStats.bind(monitor),
|
|
616
|
+
getRecords: monitor.getRecords.bind(monitor),
|
|
617
|
+
clear: monitor.clear.bind(monitor),
|
|
618
|
+
report: monitor.report.bind(monitor),
|
|
619
|
+
reportError: monitor.reportError.bind(monitor)
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Debug API
|
|
624
|
+
*/
|
|
625
|
+
export const Debug = {
|
|
626
|
+
trace: debug.trace.bind(debug),
|
|
627
|
+
getLogs: debug.getLogs.bind(debug),
|
|
628
|
+
clearLogs: debug.clearLogs.bind(debug)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
export * from './types'
|
|
632
|
+
export { Platform, MonitorConfig, DebugConfig, LogLevel, LogRequest }
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
## 5. 原生端实现
|
|
636
|
+
|
|
637
|
+
### 5.1 Android 端
|
|
638
|
+
|
|
639
|
+
```kotlin
|
|
640
|
+
// 注册 log handler
|
|
641
|
+
webView.registerHandler("log") { data, callback ->
|
|
642
|
+
val request = Gson().fromJson(data, UniLog::class.java)
|
|
643
|
+
|
|
644
|
+
// 使用 LogUtil 写入日志
|
|
645
|
+
when (request.level) {
|
|
646
|
+
"DEBUG" -> LogUtil.debug(request.tag, request.message)
|
|
647
|
+
"INFO" -> LogUtil.info(request.tag, request.message)
|
|
648
|
+
"WARN" -> LogUtil.warn(request.tag, request.message)
|
|
649
|
+
"ERROR" -> LogUtil.error(request.tag, request.message)
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
callback?.invoke(null)
|
|
653
|
+
}
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### 5.2 iOS 端
|
|
657
|
+
|
|
658
|
+
```swift
|
|
659
|
+
// 注册 log handler
|
|
660
|
+
bridge.registerHandler("log") { data, callback in
|
|
661
|
+
guard let request = data as? [String: Any],
|
|
662
|
+
let level = request["level"] as? String,
|
|
663
|
+
let message = request["message"] as? String else {
|
|
664
|
+
callback?(nil)
|
|
665
|
+
return
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
let tag = request["tag"] as? String ?? "JSBridge"
|
|
669
|
+
|
|
670
|
+
// 使用 OSLog 写入日志
|
|
671
|
+
switch level {
|
|
672
|
+
case "DEBUG":
|
|
673
|
+
os_log(.debug, log: OSLog(subsystem: "JSBridge", category: tag), "%{public}@", message)
|
|
674
|
+
case "INFO":
|
|
675
|
+
os_log(.info, log: OSLog(subsystem: "JSBridge", category: tag), "%{public}@", message)
|
|
676
|
+
case "WARN":
|
|
677
|
+
os_log(.error, log: OSLog(subsystem: "JSBridge", category: tag), "[WARN] %{public}@", message)
|
|
678
|
+
case "ERROR":
|
|
679
|
+
os_log(.fault, log: OSLog(subsystem: "JSBridge", category: tag), "%{public}@", message)
|
|
680
|
+
default:
|
|
681
|
+
break
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
callback?(nil)
|
|
685
|
+
}
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
## 6. 构建配置
|
|
689
|
+
|
|
690
|
+
### package.json
|
|
691
|
+
|
|
692
|
+
```json
|
|
693
|
+
{
|
|
694
|
+
"name": "@hd-front-end/jsbridge-sdk",
|
|
695
|
+
"version": "2.0.0",
|
|
696
|
+
"main": "dist/index.cjs.js",
|
|
697
|
+
"module": "dist/index.esm.js",
|
|
698
|
+
"types": "dist/types/index.d.ts",
|
|
699
|
+
"files": ["dist"],
|
|
700
|
+
"scripts": {
|
|
701
|
+
"build": "rollup -c",
|
|
702
|
+
"dev": "rollup -c -w",
|
|
703
|
+
"test": "jest"
|
|
704
|
+
},
|
|
705
|
+
"peerDependencies": {
|
|
706
|
+
"vconsole": "^3.15.0"
|
|
707
|
+
},
|
|
708
|
+
"peerDependenciesMeta": {
|
|
709
|
+
"vconsole": {
|
|
710
|
+
"optional": true
|
|
711
|
+
}
|
|
712
|
+
},
|
|
713
|
+
"devDependencies": {
|
|
714
|
+
"@rollup/plugin-typescript": "^11.0.0",
|
|
715
|
+
"rollup": "^3.0.0",
|
|
716
|
+
"typescript": "^5.0.0",
|
|
717
|
+
"vconsole": "^3.15.0"
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
### rollup.config.js
|
|
723
|
+
|
|
724
|
+
```javascript
|
|
725
|
+
import typescript from '@rollup/plugin-typescript'
|
|
726
|
+
|
|
727
|
+
export default {
|
|
728
|
+
input: 'src/index.ts',
|
|
729
|
+
output: [
|
|
730
|
+
{
|
|
731
|
+
file: 'dist/index.cjs.js',
|
|
732
|
+
format: 'cjs'
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
file: 'dist/index.esm.js',
|
|
736
|
+
format: 'esm'
|
|
737
|
+
}
|
|
738
|
+
],
|
|
739
|
+
plugins: [
|
|
740
|
+
typescript({
|
|
741
|
+
declaration: true,
|
|
742
|
+
declarationDir: 'dist/types'
|
|
743
|
+
})
|
|
744
|
+
],
|
|
745
|
+
external: ['vconsole']
|
|
746
|
+
}
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### tsconfig.json
|
|
750
|
+
|
|
751
|
+
```json
|
|
752
|
+
{
|
|
753
|
+
"compilerOptions": {
|
|
754
|
+
"target": "ES2015",
|
|
755
|
+
"module": "ESNext",
|
|
756
|
+
"moduleResolution": "node",
|
|
757
|
+
"declaration": true,
|
|
758
|
+
"strict": true,
|
|
759
|
+
"esModuleInterop": true,
|
|
760
|
+
"skipLibCheck": true,
|
|
761
|
+
"forceConsistentCasingInFileNames": true
|
|
762
|
+
},
|
|
763
|
+
"include": ["src/**/*"],
|
|
764
|
+
"exclude": ["node_modules", "dist"]
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
## 7. 完整使用示例
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
// App.vue
|
|
772
|
+
import { init, JSBridge, getPlatform, Monitor } from '@hd-front-end/jsbridge-sdk'
|
|
773
|
+
|
|
774
|
+
const isDev = process.env.NODE_ENV === 'development'
|
|
775
|
+
|
|
776
|
+
export default {
|
|
777
|
+
async onLaunch() {
|
|
778
|
+
// #ifdef H5-HD
|
|
779
|
+
// 1. 初始化
|
|
780
|
+
await init({
|
|
781
|
+
debug: {
|
|
782
|
+
enabled: isDev,
|
|
783
|
+
useVConsole: isDev && /vconsole=true/.test(location.search),
|
|
784
|
+
logLevel: isDev ? 'debug' : 'error'
|
|
785
|
+
},
|
|
786
|
+
monitor: {
|
|
787
|
+
enabled: !isDev,
|
|
788
|
+
autoReport: !isDev,
|
|
789
|
+
reportInterval: 60000,
|
|
790
|
+
logTag: 'JSBridge-Monitor'
|
|
791
|
+
}
|
|
792
|
+
})
|
|
793
|
+
|
|
794
|
+
console.log('平台:', getPlatform())
|
|
795
|
+
|
|
796
|
+
// 2. 记录启动日志
|
|
797
|
+
await JSBridge.log.info('App 启动成功', 'AppLifeCycle')
|
|
798
|
+
|
|
799
|
+
// 3. 注册原生回调
|
|
800
|
+
JSBridge.onRoute((data) => this.$Router.replaceAll(data))
|
|
801
|
+
JSBridge.onRefreshStore((data) => this.refreshStore(data))
|
|
802
|
+
|
|
803
|
+
// 4. 获取初始数据
|
|
804
|
+
const appInfo = await JSBridge.getAppInfo()
|
|
805
|
+
this.refreshStore(JSON.parse(appInfo), true)
|
|
806
|
+
|
|
807
|
+
// 5. 通知加载完成
|
|
808
|
+
await JSBridge.notifyAppOnload({ name: 'MyApp' })
|
|
809
|
+
// #endif
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
// 业务组件
|
|
814
|
+
export default {
|
|
815
|
+
methods: {
|
|
816
|
+
async handleScan() {
|
|
817
|
+
// 记录用户行为
|
|
818
|
+
await JSBridge.log.info('用户点击扫码', 'UserBehavior')
|
|
819
|
+
|
|
820
|
+
try {
|
|
821
|
+
const result = await JSBridge.scan()
|
|
822
|
+
|
|
823
|
+
// 记录成功
|
|
824
|
+
await JSBridge.log.info(
|
|
825
|
+
`扫码成功: ${result.resp_result}`,
|
|
826
|
+
'ScanResult'
|
|
827
|
+
)
|
|
828
|
+
} catch (error) {
|
|
829
|
+
// 记录错误
|
|
830
|
+
await JSBridge.log.error(
|
|
831
|
+
`扫码失败: ${error.message}`,
|
|
832
|
+
'ScanError'
|
|
833
|
+
)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
## 8. 代码统计
|
|
841
|
+
|
|
842
|
+
| 文件 | 行数 | 说明 |
|
|
843
|
+
|------|------|------|
|
|
844
|
+
| Bridge.ts | 200 | 核心通信 |
|
|
845
|
+
| Monitor.ts | 180 | 监控(改用log) |
|
|
846
|
+
| Debug.ts | 150 | Debug模式 |
|
|
847
|
+
| log.ts | 80 | Log API ← 新增 |
|
|
848
|
+
| device.ts | 80 | 设备API |
|
|
849
|
+
| media.ts | 90 | 媒体API |
|
|
850
|
+
| router.ts | 60 | 路由API |
|
|
851
|
+
| storage.ts | 50 | 存储API |
|
|
852
|
+
| system.ts | 40 | 系统API |
|
|
853
|
+
| types/index.ts | 120 | 类型定义 |
|
|
854
|
+
| index.ts | 100 | 统一导出 |
|
|
855
|
+
| **总计** | **~1150** | |
|
|
856
|
+
|
|
857
|
+
## 9. 相关文档
|
|
858
|
+
|
|
859
|
+
- **[00-项目概览.md](./00-项目概览.md)** - 项目概览
|
|
860
|
+
- **[01-架构设计.md](./01-架构设计.md)** - **架构设计思路**
|
|
861
|
+
- **[03-API使用文档.md](./03-API使用文档.md)** - API 详细文档
|
|
862
|
+
- **[生产级-架构设计-v2.md](./生产级-架构设计-v2.md)** - 完整架构
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
866
|
+
**完整的代码实现,可直接使用** 🎯
|
|
867
|
+
|