@anker-in/campaign-ui 0.3.5 → 0.3.6

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 (42) hide show
  1. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js +1 -1
  2. package/dist/cjs/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  3. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.d.ts +6 -2
  4. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js +1 -1
  5. package/dist/cjs/components/LiveChatWidget/hooks/useChatState.js.map +2 -2
  6. package/dist/cjs/components/LiveChatWidget/index.d.ts +1 -1
  7. package/dist/cjs/components/LiveChatWidget/index.js +1 -1
  8. package/dist/cjs/components/LiveChatWidget/index.js.map +2 -2
  9. package/dist/cjs/components/LiveChatWidget/types.d.ts +2 -0
  10. package/dist/cjs/components/LiveChatWidget/types.js.map +1 -1
  11. package/dist/cjs/components/LiveChatWidget/utils/userId.d.ts +7 -2
  12. package/dist/cjs/components/LiveChatWidget/utils/userId.js +1 -1
  13. package/dist/cjs/components/LiveChatWidget/utils/userId.js.map +3 -3
  14. package/dist/cjs/stories/LiveChatWidget.stories.js +1 -1
  15. package/dist/cjs/stories/LiveChatWidget.stories.js.map +2 -2
  16. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js +1 -1
  17. package/dist/esm/components/LiveChatWidget/LiveChatWidget.js.map +3 -3
  18. package/dist/esm/components/LiveChatWidget/hooks/useChatState.d.ts +6 -2
  19. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js +1 -1
  20. package/dist/esm/components/LiveChatWidget/hooks/useChatState.js.map +3 -3
  21. package/dist/esm/components/LiveChatWidget/index.d.ts +1 -1
  22. package/dist/esm/components/LiveChatWidget/index.js +1 -1
  23. package/dist/esm/components/LiveChatWidget/index.js.map +3 -3
  24. package/dist/esm/components/LiveChatWidget/types.d.ts +2 -0
  25. package/dist/esm/components/LiveChatWidget/utils/userId.d.ts +7 -2
  26. package/dist/esm/components/LiveChatWidget/utils/userId.js +1 -1
  27. package/dist/esm/components/LiveChatWidget/utils/userId.js.map +3 -3
  28. package/dist/esm/stories/LiveChatWidget.stories.js +1 -1
  29. package/dist/esm/stories/LiveChatWidget.stories.js.map +2 -2
  30. package/dist/index.d.mts +140 -13
  31. package/dist/index.d.ts +140 -13
  32. package/dist/index.js +2109 -6424
  33. package/dist/index.js.map +1 -1
  34. package/dist/index.mjs +1901 -6216
  35. package/dist/index.mjs.map +1 -1
  36. package/package.json +1 -1
  37. package/src/components/LiveChatWidget/LiveChatWidget.tsx +40 -7
  38. package/src/components/LiveChatWidget/hooks/useChatState.ts +18 -7
  39. package/src/components/LiveChatWidget/index.tsx +1 -1
  40. package/src/components/LiveChatWidget/types.ts +2 -0
  41. package/src/components/LiveChatWidget/utils/userId.ts +13 -62
  42. package/src/stories/LiveChatWidget.stories.tsx +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anker-in/campaign-ui",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Campaign UI components and utilities for Anker projects",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -23,6 +23,7 @@ import { useChatState } from './hooks/useChatState'
23
23
  import { useChatAPI } from './hooks/useChatAPI'
24
24
  import { MessageRendererRegistry } from './utils/messageRenderers'
25
25
  import { sanitizeInput } from './utils/validation'
26
+ import { saveUserId } from './utils/userId'
26
27
  import { transformProducts } from './utils/productTransformers.js'
27
28
  import { transformCartData } from './utils/cartTransformers.js'
28
29
  import Cookies from 'js-cookie'
@@ -175,6 +176,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
175
176
  openChat,
176
177
  closeChat,
177
178
  setInputValue,
179
+ setUserId,
178
180
  addMessage,
179
181
  setMessages,
180
182
  clearMessages,
@@ -232,6 +234,9 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
232
234
  return registry
233
235
  }, [customRenderers])
234
236
 
237
+ // 标记是否已经初始化过会话,防止重复调用
238
+ const sessionInitializedRef = React.useRef(false)
239
+
235
240
  /**
236
241
  * T043: 打开聊天窗口时初始化会话
237
242
  * 使用 API v2.0.0 的统一接口:
@@ -239,7 +244,18 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
239
244
  * - 如果有 sessionId,恢复会话并加载历史消息
240
245
  */
241
246
  useEffect(() => {
242
- if (!isOpen || !userId) return
247
+ // 窗口关闭时重置初始化标记
248
+ if (!isOpen) {
249
+ sessionInitializedRef.current = false
250
+ return
251
+ }
252
+
253
+ // userId 为 undefined 表示尚未初始化,等待初始化完成
254
+ if (userId === undefined) return
255
+
256
+ // 如果已经初始化过,不再重复调用
257
+ if (sessionInitializedRef.current) return
258
+ sessionInitializedRef.current = true
243
259
 
244
260
  const currentSessionId = sessionId
245
261
 
@@ -372,7 +388,8 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
372
388
  * 创建新会话
373
389
  */
374
390
  const handleCreateNewSession = useCallback(async () => {
375
- if (!userId) return
391
+ // userId 为 undefined 表示尚未初始化,等待初始化完成
392
+ if (userId === undefined) return
376
393
 
377
394
  // 如果用户没有配置欢迎语,显示 loading 状态
378
395
  if (!welcomeMessage) {
@@ -381,16 +398,23 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
381
398
 
382
399
  try {
383
400
  const response = await createSession({
384
- user_id: userId,
401
+ user_id: userId ?? '',
385
402
  site: site,
386
403
  channel_code: channelCode,
387
404
  real_user_id: loginUserId,
405
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
388
406
  })
389
407
 
390
408
  if (response.success) {
391
409
  // 保存新会话 ID
392
410
  saveSession(response.sessionId)
393
411
 
412
+ // 保存后端返回的 userId(如果有)
413
+ if (response.userId) {
414
+ saveUserId(response.userId)
415
+ setUserId(response.userId)
416
+ }
417
+
394
418
  // 清空消息列表
395
419
  clearMessages()
396
420
 
@@ -488,16 +512,23 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
488
512
 
489
513
  try {
490
514
  const response = await createSession({
491
- user_id: userId,
515
+ user_id: userId ?? '',
492
516
  session_id: existingSessionId,
493
517
  site: site,
494
518
  channel_code: channelCode,
495
519
  real_user_id: loginUserId,
520
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
496
521
  })
497
522
 
498
523
  if (response.success && response.resumed) {
499
524
  // 会话恢复成功
500
525
 
526
+ // 保存后端返回的 userId(如果有)
527
+ if (response.userId) {
528
+ saveUserId(response.userId)
529
+ setUserId(response.userId)
530
+ }
531
+
501
532
  // 准备欢迎消息(无论是否有历史消息都需要)
502
533
  const messageText = response.welcomeMessage || welcomeMessage
503
534
  const welcomeContent: MessageContent[] = messageText ? [{ type: 'text', text: messageText }] : []
@@ -683,10 +714,11 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
683
714
  if (!currentSessionId) {
684
715
  // 没有会话,创建新会话
685
716
  const response = await createSession({
686
- user_id: userId,
717
+ user_id: userId ?? '',
687
718
  site: site,
688
719
  channel_code: channelCode,
689
720
  real_user_id: loginUserId,
721
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
690
722
  })
691
723
  if (response.success) {
692
724
  currentSessionId = response.sessionId
@@ -699,7 +731,7 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
699
731
  // 构建请求参数(session_id 现在是必填的)
700
732
  const requestPayload: ChatStreamRequest = {
701
733
  message: sanitized,
702
- user_id: userId,
734
+ user_id: userId ?? '',
703
735
  session_id: currentSessionId,
704
736
  context: {
705
737
  cartId: cartId,
@@ -730,10 +762,11 @@ export const LiveChatWidget: React.FC<LiveChatWidgetProps> = ({
730
762
 
731
763
  // 创建新会话
732
764
  const response = await createSession({
733
- user_id: userId,
765
+ user_id: userId ?? '',
734
766
  site: site,
735
767
  channel_code: channelCode,
736
768
  real_user_id: loginUserId,
769
+ page_url: typeof window !== 'undefined' ? window.location.href : undefined,
737
770
  })
738
771
 
739
772
  if (response.success) {
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { useState, useCallback, useRef, useEffect } from 'react'
8
8
  import type { Message, MessageContent, SSEEvent, StatusData, ErrorData, MessageStartData, BackendCartData, Product, TextContent, ProductCardContent, ProductListContent } from '../types'
9
- import { getUserId } from '../utils/userId'
9
+ import { getUserId, saveUserId } from '../utils/userId'
10
10
  import { useSession } from './useSession'
11
11
  import { transformProducts } from '../utils/productTransformers'
12
12
  import { transformCartData } from '../utils/cartTransformers'
@@ -391,9 +391,9 @@ export interface UseChatStateReturn {
391
391
  isOpen: boolean
392
392
 
393
393
  /**
394
- * 用户 ID
394
+ * 用户 ID(undefined 表示尚未初始化,空字符串表示由后端生成)
395
395
  */
396
- userId: string
396
+ userId: string | undefined
397
397
 
398
398
  /**
399
399
  * 会话 ID
@@ -430,6 +430,11 @@ export interface UseChatStateReturn {
430
430
  */
431
431
  setInputValue: (value: string) => void
432
432
 
433
+ /**
434
+ * 设置用户 ID(用于保存后端返回的 userId)
435
+ */
436
+ setUserId: (id: string) => void
437
+
433
438
  /**
434
439
  * 添加消息到列表
435
440
  */
@@ -495,8 +500,8 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
495
500
  // 会话管理
496
501
  const { sessionId, saveSession, clearSession } = useSession()
497
502
 
498
- // 用户 ID (初始化时异步生成)
499
- const [userId, setUserId] = useState<string>('')
503
+ // 用户 ID (初始化时异步生成,undefined 表示尚未初始化)
504
+ const [userId, setUserId] = useState<string | undefined>(undefined)
500
505
 
501
506
  // 初始化 userId
502
507
  useEffect(() => {
@@ -646,11 +651,16 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
646
651
  // 重置卡片缓存队列
647
652
  pendingCardsRef.current = []
648
653
 
649
- // T039: 保存 sessionId(如果后端返回)
650
- const messageStartData = data as MessageStartData
654
+ // T039: 保存 sessionId 和 userId(如果后端返回)
655
+ const messageStartData = data as MessageStartData & { userId?: string }
651
656
  if (messageStartData.sessionId && messageStartData.sessionId !== sessionId) {
652
657
  saveSession(messageStartData.sessionId)
653
658
  }
659
+ // 保存后端返回的 userId(如果有)
660
+ if (messageStartData.userId) {
661
+ saveUserId(messageStartData.userId)
662
+ setUserId(messageStartData.userId)
663
+ }
654
664
 
655
665
  // 检查最后一条消息是否是 thinking 消息(用户发送消息时已添加)
656
666
  setMessagesState(prev => {
@@ -1081,6 +1091,7 @@ export function useChatState(options: UseChatStateOptions = {}): UseChatStateRet
1081
1091
  closeChat,
1082
1092
  toggleChat,
1083
1093
  setInputValue,
1094
+ setUserId,
1084
1095
  addMessage,
1085
1096
  setMessages,
1086
1097
  clearMessages,
@@ -44,7 +44,7 @@ export { useSession } from './hooks/useSession'
44
44
 
45
45
  // 导出工具类
46
46
  export { MessageRendererRegistry } from './utils/messageRenderers'
47
- export { getUserId } from './utils/userId'
47
+ export { getUserId, saveUserId, clearUserId } from './utils/userId'
48
48
  export { sanitizeInput, isValidUrl, isValidUUID, isValidMessageContent, escapeHtml } from './utils/validation'
49
49
 
50
50
  // 导出消息渲染器(供自定义使用)
@@ -637,11 +637,13 @@ export interface NewSessionRequest {
637
637
  site?: string
638
638
  channel_code?: string
639
639
  real_user_id?: string
640
+ page_url?: string
640
641
  }
641
642
 
642
643
  export interface NewSessionResponse {
643
644
  success: boolean
644
645
  sessionId: string
646
+ userId?: string // 后端生成的 userId(当请求的 user_id 为空时返回)
645
647
  message: string
646
648
  resumed?: boolean
647
649
  messages?: Message[]
@@ -5,14 +5,14 @@
5
5
  * 策略:
6
6
  * 1. 优先从 localStorage 读取
7
7
  * 2. 尝试获取 Google Analytics ID (GAID)
8
- * 3. 兜底:时间戳 + 随机数哈希
8
+ * 3. 返回空字符串,由后端生成
9
9
  */
10
10
 
11
11
  const STORAGE_KEY = 'livechat_user_id'
12
12
 
13
13
  /**
14
14
  * 获取用户唯一标识符(异步版本)
15
- * @returns userId (GAID 或哈希值)
15
+ * @returns userId (GAID 或空字符串,空字符串由后端生成)
16
16
  */
17
17
  export async function getUserId(): Promise<string> {
18
18
  // 1. 尝试从 localStorage 读取
@@ -30,21 +30,17 @@ export async function getUserId(): Promise<string> {
30
30
  return gaid
31
31
  }
32
32
 
33
- // 3. 兜底:使用 SHA-256 生成哈希
34
- try {
35
- const fallback = await generateHashedUserId()
36
- if (typeof window !== 'undefined') {
37
- localStorage.setItem(STORAGE_KEY, fallback)
38
- }
39
- return fallback
40
- } catch (error) {
41
- // 如果 SHA-256 失败,使用同步兜底方案
42
- console.warn('[LiveChat userId] SHA-256 failed, using sync fallback:', error)
43
- const fallback = generateHashedUserIdSync()
44
- if (typeof window !== 'undefined') {
45
- localStorage.setItem(STORAGE_KEY, fallback)
46
- }
47
- return fallback
33
+ // 3. 返回空字符串,由后端生成
34
+ return ''
35
+ }
36
+
37
+ /**
38
+ * 保存后端返回的 userId 到 localStorage
39
+ * @param id 后端生成的 userId
40
+ */
41
+ export function saveUserId(id: string): void {
42
+ if (id && typeof window !== 'undefined') {
43
+ localStorage.setItem(STORAGE_KEY, id)
48
44
  }
49
45
  }
50
46
 
@@ -85,51 +81,6 @@ function getGAID(): string | null {
85
81
  }
86
82
  }
87
83
 
88
- /**
89
- * 生成哈希用户 ID(兜底方案)
90
- * @returns 格式为 user-{hash} 的用户 ID,hash 由 timestamp + random 通过 SHA-256 生成
91
- */
92
- async function generateHashedUserId(): Promise<string> {
93
- const timestamp = Date.now()
94
- const random = Math.random().toString(36).substring(2, 15) // 生成随机字符串
95
-
96
- // 将 timestamp 和 random 组合成字符串
97
- const rawString = `${timestamp}${random}`
98
-
99
- // 使用 Web Crypto API 生成 SHA-256 哈希
100
- const encoder = new TextEncoder()
101
- const data = encoder.encode(rawString)
102
- const hashBuffer = await crypto.subtle.digest('SHA-256', data)
103
-
104
- // 将 ArrayBuffer 转换为 16 进制字符串
105
- const hashArray = Array.from(new Uint8Array(hashBuffer))
106
- const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('')
107
-
108
- // 取前 16 位作为用户 ID(保持简洁)
109
- return `user-${hashHex.substring(0, 16)}`
110
- }
111
-
112
- /**
113
- * 同步版本的哈希用户 ID 生成(兜底的兜底)
114
- * 当 Web Crypto API 不可用时使用
115
- */
116
- function generateHashedUserIdSync(): string {
117
- const timestamp = Date.now()
118
- const random = Math.random().toString(36).substring(2, 15)
119
- const rawString = `${timestamp}${random}`
120
-
121
- // 使用简单的哈希算法作为兜底
122
- let hash = 0
123
- for (let i = 0; i < rawString.length; i++) {
124
- const char = rawString.charCodeAt(i)
125
- hash = (hash << 5) - hash + char
126
- hash = hash & hash
127
- }
128
-
129
- const hashString = Math.abs(hash).toString(16).padStart(8, '0')
130
- return `user-${hashString}`
131
- }
132
-
133
84
  /**
134
85
  * 清除保存的 userId(用于测试或重置)
135
86
  */
@@ -148,7 +148,7 @@ export const Default: Story = {
148
148
  args: {
149
149
  // 基础配置
150
150
  loginUserId: 'test_test1',
151
- apiBaseUrl: 'http://172.16.38.183:3003',
151
+ apiBaseUrl: 'https://beta-api-v2-livechat.anker.com',
152
152
  site: 'beta.eufy.com',
153
153
  channelCode: 'dtc',
154
154
  title: 'eufy AI Assistant',