@beppla/tapas-ui 1.2.8 → 1.2.10

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.
Files changed (33) hide show
  1. package/commonjs/LineChart/LineChart.js +76 -3
  2. package/commonjs/LineChart/LineChart.js.map +1 -1
  3. package/commonjs/WebViewBridge/README.md +536 -0
  4. package/commonjs/WebViewBridge/WebViewBridge.js +741 -0
  5. package/commonjs/WebViewBridge/WebViewBridge.js.map +1 -0
  6. package/commonjs/WebViewBridge/index.js +32 -0
  7. package/commonjs/WebViewBridge/index.js.map +1 -0
  8. package/commonjs/WebViewBridge/useWebViewBridge.js +89 -0
  9. package/commonjs/WebViewBridge/useWebViewBridge.js.map +1 -0
  10. package/commonjs/index.js +31 -0
  11. package/commonjs/index.js.map +1 -1
  12. package/module/LineChart/LineChart.js +76 -3
  13. package/module/LineChart/LineChart.js.map +1 -1
  14. package/module/WebViewBridge/README.md +536 -0
  15. package/module/WebViewBridge/WebViewBridge.js +733 -0
  16. package/module/WebViewBridge/WebViewBridge.js.map +1 -0
  17. package/module/WebViewBridge/index.js +5 -0
  18. package/module/WebViewBridge/index.js.map +1 -0
  19. package/module/WebViewBridge/useWebViewBridge.js +83 -0
  20. package/module/WebViewBridge/useWebViewBridge.js.map +1 -0
  21. package/module/index.js +2 -0
  22. package/module/index.js.map +1 -1
  23. package/package.json +1 -1
  24. package/typescript/LineChart/LineChart.d.ts +11 -0
  25. package/typescript/LineChart/LineChart.d.ts.map +1 -1
  26. package/typescript/WebViewBridge/WebViewBridge.d.ts +132 -0
  27. package/typescript/WebViewBridge/WebViewBridge.d.ts.map +1 -0
  28. package/typescript/WebViewBridge/index.d.ts +3 -0
  29. package/typescript/WebViewBridge/index.d.ts.map +1 -0
  30. package/typescript/WebViewBridge/useWebViewBridge.d.ts +55 -0
  31. package/typescript/WebViewBridge/useWebViewBridge.d.ts.map +1 -0
  32. package/typescript/index.d.ts +2 -0
  33. package/typescript/index.d.ts.map +1 -1
@@ -0,0 +1,536 @@
1
+ # WebViewBridge 组件
2
+
3
+ WebView/iframe 通信桥接组件,用于 RN Web 模块与 tapas-sys 底座之间的通信。
4
+
5
+ ## 功能特性
6
+
7
+ - ✅ **自动环境检测**:自动判断运行在 Android/iOS WebView 或 Web iframe 环境
8
+ - ✅ **统一消息格式**:自动适配不同环境的消息格式差异
9
+ - ✅ **消息监听**:支持全局和类型特定的消息监听
10
+ - ✅ **自动初始化**:组件挂载后,在 window.onload 完成时自动发送初始化消息给 tapas-sys(默认行为)
11
+ - ✅ **类型安全**:完整的 TypeScript 类型定义
12
+ - ✅ **易于使用**:提供 Hook 简化使用
13
+
14
+ ## 安装
15
+
16
+ ```bash
17
+ yarn add @beppla/tapas-ui
18
+ # 或
19
+ npm install @beppla/tapas-ui
20
+ ```
21
+
22
+ ## 基础用法
23
+
24
+ ### 方式一:使用组件
25
+
26
+ ```tsx
27
+ import React from 'react';
28
+ import { WebViewBridge, WebViewMessage, WebViewBridgeRef } from '@beppla/tapas-ui';
29
+
30
+ function MyComponent() {
31
+ const bridgeRef = React.useRef<WebViewBridgeRef>(null);
32
+
33
+ const handleMessage = (message: WebViewMessage) => {
34
+ console.log('收到消息:', message);
35
+ console.log('消息来源:', message.from); // 来自 tapas-sys 的消息
36
+
37
+ if (message.type === 'userInfo') {
38
+ console.log('用户信息:', message.payload);
39
+ }
40
+ };
41
+
42
+ const sendDataToTapasSys = () => {
43
+ // 向 tapas-sys 发送消息(自动包含 from 字段并 base64 加密)
44
+ if (bridgeRef.current) {
45
+ bridgeRef.current.sendMessage('requestData', {
46
+ moduleId: 'myModule',
47
+ action: 'getUserInfo'
48
+ });
49
+ }
50
+ };
51
+
52
+ return (
53
+ <>
54
+ <WebViewBridge
55
+ ref={bridgeRef}
56
+ from="MyRNWebModule" // 必需:标识消息来源
57
+ onMessage={handleMessage}
58
+ initPayload={{ moduleName: 'MyModule', version: '1.0.0' }}
59
+ autoInit={true}
60
+ enableBase64={true} // 默认启用 base64 加密
61
+ />
62
+ <button onClick={sendDataToTapasSys}>发送消息到 tapas-sys</button>
63
+ </>
64
+ );
65
+ }
66
+ ```
67
+
68
+ ### 方式二:使用 Hook(推荐)
69
+
70
+ ```tsx
71
+ import React from 'react';
72
+ import { useWebViewBridge } from '@beppla/tapas-ui';
73
+
74
+ function MyComponent() {
75
+ const { bridgeRef, sendMessage, handleMessage } = useWebViewBridge({
76
+ onMessage: (message) => {
77
+ console.log('收到消息:', message);
78
+ console.log('消息来源:', message.from);
79
+ },
80
+ messageHandlers: {
81
+ 'userInfo': (message) => {
82
+ console.log('用户信息:', message.payload);
83
+ },
84
+ 'config': (message) => {
85
+ console.log('配置信息:', message.payload);
86
+ },
87
+ },
88
+ });
89
+
90
+ const handleButtonClick = () => {
91
+ // 发送的消息会自动包含 from 字段并 base64 加密
92
+ sendMessage('requestData', { id: 123 });
93
+ };
94
+
95
+ return (
96
+ <>
97
+ <WebViewBridge
98
+ ref={bridgeRef}
99
+ from="MyRNWebModule" // 必需:标识消息来源
100
+ onMessage={handleMessage}
101
+ initPayload={{ moduleName: 'MyModule' }}
102
+ enableBase64={true} // 默认启用 base64 加密
103
+ />
104
+ <button onClick={handleButtonClick}>请求数据</button>
105
+ </>
106
+ );
107
+ }
108
+ ```
109
+
110
+ ## API 参考
111
+
112
+ ### WebViewBridge Props
113
+
114
+ | 属性 | 类型 | 默认值 | 说明 |
115
+ |------|------|--------|------|
116
+ | `from` | `string` | - | **消息来源标识**(**必需**),用于标识 RN Web 模块,会作为消息的 from 字段发送给 tapas-sys |
117
+ | `onMessage` | `(message: WebViewMessage) => void` | - | 消息监听回调 |
118
+ | `onError` | `(error: Error, context?: { type: 'send' \| 'receive' \| 'parse' \| 'origin' }) => void` | - | **错误回调**,当发送/接收/解析消息或 origin 验证失败时触发 |
119
+ | `initPayload` | `any` | - | 初始化消息的 payload |
120
+ | `autoInit` | `boolean` | `true` | **是否自动发送初始化消息**(默认 true)。组件会在 window.onload 完成后自动发送初始化消息给 tapas-sys |
121
+ | `initMessageType` | `string` | `'init'` | 初始化消息类型(默认 'init') |
122
+ | `enableBase64` | `boolean` | `true` | **是否启用 base64 加密**(默认 true,符合 tapas-sys 解析格式) |
123
+ | `showPlaceholder` | `boolean` | `true` | 是否显示占位 DOM |
124
+ | `style` | `StyleProp<ViewStyle>` | - | 自定义样式 |
125
+ | `testID` | `string` | - | 测试 ID |
126
+ | `allowedOrigins` | `string[]` | - | **安全性:允许的消息来源(origin)白名单**。在 iframe 环境中,只有来自这些 origin 的消息才会被处理。支持通配符(如 `'https://*.example.com'`) |
127
+ | `strictOriginCheck` | `boolean` | `false` | **安全性:是否严格验证 origin**。`true`: 如果消息来源不在白名单中,直接拒绝;`false`: 仅警告,但仍处理消息(开发环境) |
128
+ | `allowedFrom` | `string[]` | - | **安全性:只接收来自特定 from 的消息**。如果设置,只有 from 字段匹配的消息才会被处理 |
129
+ | `messageFilter` | `(message: WebViewMessage) => boolean` | - | **消息过滤器函数**。返回 `true` 表示处理该消息,`false` 表示忽略 |
130
+ | `debug` | `boolean` | `false` | **调试模式**。启用后会输出详细的日志信息 |
131
+ | `logLevel` | `'none' \| 'error' \| 'warn' \| 'info' \| 'debug'` | `'warn'` | **日志级别**。控制输出的日志详细程度 |
132
+
133
+ ### WebViewBridgeRef 方法
134
+
135
+ | 方法 | 说明 |
136
+ |------|------|
137
+ | `sendMessage(type: string, payload?: any): boolean` | **发送消息到 tapas-sys 底座**(支持 WebView 和 iframe 两种环境)。返回 `true` 表示发送成功,`false` 表示失败 |
138
+ | `getEnvironment(): 'webview' \| 'iframe' \| 'unknown'` | 获取当前运行环境 |
139
+ | `cleanup(): void` | **清理所有监听器和资源**。组件卸载时会自动调用,也可手动调用 |
140
+
141
+ ### 发送消息到 tapas-sys
142
+
143
+ 组件提供了完整的消息发送功能,**RN Web 模块可以通过以下方式向 tapas-sys 发送消息**:
144
+
145
+ ```tsx
146
+ // 方式一:通过 ref
147
+ const bridgeRef = useRef<WebViewBridgeRef>(null);
148
+
149
+ bridgeRef.current?.sendMessage('requestData', { id: 123 });
150
+
151
+ // 方式二:通过 Hook
152
+ const { sendMessage } = useWebViewBridge();
153
+
154
+ sendMessage('requestData', { id: 123 });
155
+ ```
156
+
157
+ **发送机制**:
158
+ - **Android/iOS WebView**:使用 `window.ReactNativeWebView.postMessage()`
159
+ - **Web iframe**:使用 `window.parent.postMessage()`
160
+ - **自动降级**:如果主要方式失败,会自动尝试其他方式
161
+
162
+ ### WebViewMessage 类型
163
+
164
+ ```typescript
165
+ interface WebViewMessage {
166
+ type: string; // 消息类型
167
+ payload?: any; // 消息数据
168
+ timestamp?: number; // 时间戳
169
+ id?: string; // 消息 ID
170
+ from?: string; // 消息来源标识(RN Web 模块标识)
171
+ }
172
+ ```
173
+
174
+ ## 原生 WebView 集成
175
+
176
+ ### React Native WebView
177
+
178
+ 如果使用 `react-native-webview`,需要配置如下:
179
+
180
+ ```tsx
181
+ import { WebView } from 'react-native-webview';
182
+ import { WebViewBridge, handleNativeWebViewMessage } from '@beppla/tapas-ui';
183
+
184
+ function App() {
185
+ const handleWebViewMessage = (event: any) => {
186
+ handleNativeWebViewMessage(event, (message) => {
187
+ console.log('收到消息:', message);
188
+ });
189
+ };
190
+
191
+ return (
192
+ <WebView
193
+ source={{ uri: 'https://your-app.com' }}
194
+ onMessage={handleWebViewMessage}
195
+ injectedJavaScript={createWebViewInjectedScript()}
196
+ />
197
+ );
198
+ }
199
+ ```
200
+
201
+ ### Expo WebView
202
+
203
+ ```tsx
204
+ import { WebView } from 'react-native-webview';
205
+ import { WebViewBridge, createWebViewInjectedScript } from '@beppla/tapas-ui';
206
+
207
+ function App() {
208
+ return (
209
+ <WebView
210
+ source={{ uri: 'https://your-app.com' }}
211
+ injectedJavaScript={createWebViewInjectedScript()}
212
+ />
213
+ );
214
+ }
215
+ ```
216
+
217
+ ## 消息格式
218
+
219
+ ### 发送消息
220
+
221
+ ```typescript
222
+ // 使用 from 字段标识消息来源
223
+ <WebViewBridge
224
+ from="MyRNWebModule"
225
+ enableBase64={true}
226
+ />
227
+
228
+ // 发送消息
229
+ sendMessage('requestData', { id: 123 });
230
+
231
+ // 实际构建的消息对象:
232
+ {
233
+ type: 'requestData',
234
+ payload: { id: 123 },
235
+ timestamp: 1234567890,
236
+ id: 'msg_1234567890_1',
237
+ from: 'MyRNWebModule' // 自动添加
238
+ }
239
+
240
+ // 发送流程:
241
+ // 1. JSON.stringify(message) → JSON 字符串
242
+ // 2. Base64 编码 → base64 字符串
243
+ // 3. 通过 postMessage 发送给 tapas-sys
244
+ ```
245
+
246
+ ### Base64 加密
247
+
248
+ 组件默认启用 base64 加密,符合 tapas-sys 的解析格式:
249
+
250
+ ```typescript
251
+ // 启用 base64(默认)
252
+ <WebViewBridge from="MyModule" enableBase64={true} />
253
+
254
+ // 禁用 base64(仅用于调试)
255
+ <WebViewBridge from="MyModule" enableBase64={false} />
256
+ ```
257
+
258
+ **消息加密流程**:
259
+ 1. 构建消息对象(包含 `from` 字段)
260
+ 2. `JSON.stringify()` 转为 JSON 字符串
261
+ 3. Base64 编码
262
+ 4. 通过 `postMessage` 发送
263
+
264
+ **消息解密流程**(接收时):
265
+ 1. 接收 base64 字符串
266
+ 2. Base64 解码
267
+ 3. `JSON.parse()` 解析
268
+ 4. 触发 `onMessage` 回调
269
+
270
+ ### 接收消息
271
+
272
+ 组件会自动处理不同环境的消息格式差异,并自动进行 base64 解码:
273
+
274
+ - **WebView 环境**:从 `event.nativeEvent.data` 或 `event.data` 解析(自动 base64 解码)
275
+ - **iframe 环境**:从 `event.data` 解析(自动 base64 解码)
276
+
277
+ **注意**:如果消息不是 base64 编码的,组件会自动降级为直接 JSON 解析
278
+
279
+ ### 自动初始化消息
280
+
281
+ **组件会在 window.onload 完成后自动发送初始化消息给 tapas-sys**,这是组件的默认行为,无需手动调用:
282
+
283
+ ```tsx
284
+ <WebViewBridge
285
+ from="MyRNWebModule" // 必需:标识消息来源
286
+ autoInit={true} // 默认 true,在 window.onload 完成后自动发送
287
+ initMessageType="init" // 默认 'init'
288
+ initPayload={{ moduleName: 'MyModule', version: '1.0.0' }}
289
+ enableBase64={true} // 默认 true,消息会进行 base64 编码
290
+ />
291
+ ```
292
+
293
+ **初始化流程**:
294
+ 1. 组件挂载后,检测 `document.readyState`
295
+ 2. 如果文档已加载完成(`complete` 或 `interactive`),立即发送初始化消息
296
+ 3. 如果文档还在加载,等待 `window.onload` 事件
297
+ 4. 发送的消息格式:`{ type: 'init', payload: {...}, from: 'MyRNWebModule', ... }`
298
+ 5. 消息会自动进行 base64 编码后发送给 tapas-sys
299
+
300
+ **重要说明**:
301
+ - `from` 字段是**必需的**,用于标识 RN Web 模块,tapas-sys 需要此字段来识别消息来源
302
+ - 初始化消息会在 RN web 项目完全加载后(window.onload)自动发送,确保所有资源都已就绪
303
+ - 如果不需要自动初始化,可以设置 `autoInit={false}`,然后手动调用 `sendMessage('init', {...})`
304
+
305
+ ## 运行环境检测
306
+
307
+ 组件会自动检测运行环境:
308
+
309
+ - **Android/iOS**:检测为 `'webview'`
310
+ - **Web(iframe)**:检测为 `'iframe'`
311
+ - **未知环境**:检测为 `'unknown'`
312
+
313
+ ```typescript
314
+ const { getEnvironment } = useWebViewBridge();
315
+
316
+ const env = getEnvironment();
317
+ console.log('当前环境:', env); // 'webview' | 'iframe' | 'unknown'
318
+ ```
319
+
320
+ ## 最佳实践
321
+
322
+ ### 1. 使用 Hook 简化代码
323
+
324
+ ```tsx
325
+ const { bridgeRef, sendMessage, handleMessage } = useWebViewBridge({
326
+ messageHandlers: {
327
+ 'userInfo': handleUserInfo,
328
+ 'config': handleConfig,
329
+ },
330
+ });
331
+ ```
332
+
333
+ ### 2. 注册特定类型的处理器
334
+
335
+ ```tsx
336
+ const { registerHandler } = useWebViewBridge();
337
+
338
+ useEffect(() => {
339
+ const unregister = registerHandler('customType', (message) => {
340
+ // 处理特定类型的消息
341
+ });
342
+
343
+ return unregister; // 清理
344
+ }, []);
345
+ ```
346
+
347
+ ### 3. 错误处理
348
+
349
+ ```tsx
350
+ const handleMessage = (message: WebViewMessage) => {
351
+ try {
352
+ // 处理消息
353
+ } catch (error) {
354
+ console.error('消息处理错误:', error);
355
+ sendMessage('error', { error: error.message });
356
+ }
357
+ };
358
+ ```
359
+
360
+ ### 4. 消息确认机制
361
+
362
+ ```tsx
363
+ const sendMessageWithAck = async (type: string, payload: any) => {
364
+ const messageId = `msg_${Date.now()}`;
365
+
366
+ return new Promise((resolve, reject) => {
367
+ const timeout = setTimeout(() => {
368
+ reject(new Error('消息超时'));
369
+ }, 5000);
370
+
371
+ const handler = (message: WebViewMessage) => {
372
+ if (message.id === messageId) {
373
+ clearTimeout(timeout);
374
+ resolve(message);
375
+ }
376
+ };
377
+
378
+ registerHandler('ack', handler);
379
+ sendMessage(type, { ...payload, messageId });
380
+ });
381
+ };
382
+ ```
383
+
384
+ ## 安全性配置
385
+
386
+ ### Origin 验证
387
+
388
+ 在生产环境中,强烈建议配置 `allowedOrigins` 和 `strictOriginCheck` 来确保消息来源可信:
389
+
390
+ ```tsx
391
+ <WebViewBridge
392
+ from="MyRNWebModule"
393
+ allowedOrigins={[
394
+ 'https://tapas-sys.example.com',
395
+ 'https://*.example.com', // 支持通配符
396
+ ]}
397
+ strictOriginCheck={true} // 生产环境建议设为 true
398
+ onError={(error, context) => {
399
+ if (context?.type === 'origin') {
400
+ console.error('消息来源验证失败:', error);
401
+ // 可以上报安全事件
402
+ }
403
+ }}
404
+ />
405
+ ```
406
+
407
+ ### 消息来源过滤
408
+
409
+ 可以限制只接收来自特定 `from` 的消息:
410
+
411
+ ```tsx
412
+ <WebViewBridge
413
+ from="MyRNWebModule"
414
+ allowedFrom={['tapas-sys', 'other-module']} // 只接收来自这些模块的消息
415
+ onMessage={handleMessage}
416
+ />
417
+ ```
418
+
419
+ ### 消息过滤器
420
+
421
+ 使用自定义过滤器函数来过滤消息:
422
+
423
+ ```tsx
424
+ <WebViewBridge
425
+ from="MyRNWebModule"
426
+ messageFilter={(message) => {
427
+ // 只处理特定类型的消息
428
+ return message.type === 'userInfo' || message.type === 'config';
429
+ }}
430
+ onMessage={handleMessage}
431
+ />
432
+ ```
433
+
434
+ ## 调试和日志
435
+
436
+ ### 调试模式
437
+
438
+ 启用调试模式可以查看详细的日志信息:
439
+
440
+ ```tsx
441
+ <WebViewBridge
442
+ from="MyRNWebModule"
443
+ debug={true} // 启用调试模式
444
+ logLevel="debug" // 输出所有日志
445
+ onMessage={handleMessage}
446
+ />
447
+ ```
448
+
449
+ ### 日志级别
450
+
451
+ - `'none'`: 不输出任何日志
452
+ - `'error'`: 仅输出错误
453
+ - `'warn'`: 输出警告和错误(默认)
454
+ - `'info'`: 输出信息、警告和错误
455
+ - `'debug'`: 输出所有日志(包括调试信息)
456
+
457
+ ## 错误处理
458
+
459
+ ### 错误回调
460
+
461
+ 使用 `onError` 回调来处理各种错误:
462
+
463
+ ```tsx
464
+ <WebViewBridge
465
+ from="MyRNWebModule"
466
+ onError={(error, context) => {
467
+ switch (context?.type) {
468
+ case 'send':
469
+ console.error('发送消息失败:', error);
470
+ // 可以重试或上报
471
+ break;
472
+ case 'receive':
473
+ console.error('接收消息失败:', error);
474
+ break;
475
+ case 'parse':
476
+ console.error('解析消息失败:', error);
477
+ break;
478
+ case 'origin':
479
+ console.error('Origin 验证失败:', error);
480
+ // 安全事件,应该上报
481
+ break;
482
+ }
483
+ }}
484
+ />
485
+ ```
486
+
487
+ ### 发送状态检查
488
+
489
+ `sendMessage` 现在返回布尔值,表示是否发送成功:
490
+
491
+ ```tsx
492
+ const bridgeRef = useRef<WebViewBridgeRef>(null);
493
+
494
+ const handleSend = () => {
495
+ if (bridgeRef.current) {
496
+ const success = bridgeRef.current.sendMessage('requestData', { id: 123 });
497
+ if (!success) {
498
+ console.error('消息发送失败');
499
+ // 可以重试
500
+ }
501
+ }
502
+ };
503
+ ```
504
+
505
+ ## 注意事项
506
+
507
+ 1. **安全性**:在生产环境中,**必须**配置 `allowedOrigins` 和 `strictOriginCheck={true}` 确保消息来源可信
508
+ 2. **性能**:避免在消息处理器中执行耗时操作
509
+ 3. **内存泄漏**:记得清理注册的处理器,组件卸载时会自动清理
510
+ 4. **兼容性**:确保父窗口/WebView 支持 postMessage API
511
+ 5. **调试**:开发环境可以使用 `debug={true}` 和 `logLevel="debug"` 来查看详细日志,生产环境建议使用 `logLevel="error"` 或 `"warn"`
512
+
513
+ ## 故障排查
514
+
515
+ ### 消息无法发送
516
+
517
+ 1. 检查运行环境是否正确检测
518
+ 2. 确认父窗口/WebView 存在
519
+ 3. 检查控制台是否有错误信息
520
+
521
+ ### 消息无法接收
522
+
523
+ 1. 确认已正确配置 `onMessage` 回调
524
+ 2. 检查消息格式是否正确
525
+ 3. 验证事件监听器是否已注册
526
+
527
+ ### 初始化消息未发送
528
+
529
+ 1. 确认 `autoInit` 为 `true`
530
+ 2. 检查组件是否已完全渲染
531
+ 3. 查看控制台是否有错误
532
+
533
+ ## 示例
534
+
535
+ 完整示例请参考 Storybook 中的 `WebViewBridge` 组件示例。
536
+