@tencentcloud/web-push 1.0.3 → 1.0.4
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/CHANGELOG.md +63 -0
- package/README.md +100 -92
- package/{dist/index.esm.js → index.esm.js} +1 -1
- package/{dist/index.umd.js → index.umd.js} +1 -1
- package/package.json +9 -47
- package/src/__tests__/index.test.ts +0 -120
- package/src/__tests__/integration.test.ts +0 -285
- package/src/__tests__/setup.ts +0 -210
- package/src/__tests__/types.test.ts +0 -303
- package/src/__tests__/web-push-sdk.test.ts +0 -257
- package/src/components/message-popup.ts +0 -1007
- package/src/core/event-emitter.ts +0 -61
- package/src/core/service-worker-manager.ts +0 -614
- package/src/core/web-push-sdk.ts +0 -690
- package/src/debug/GenerateTestUserSig.js +0 -37
- package/src/debug/index.d.ts +0 -6
- package/src/debug/index.js +0 -1
- package/src/debug/lib-generate-test-usersig-es.min.js +0 -2
- package/src/index.ts +0 -9
- package/src/service-worker/sw.ts +0 -494
- package/src/types/index.ts +0 -2
- package/src/types/inner.ts +0 -44
- package/src/types/outer.ts +0 -142
- package/src/utils/browser-support.ts +0 -412
- package/src/utils/logger.ts +0 -66
- package/src/utils/storage.ts +0 -51
- package/src/utils/validator.ts +0 -267
- /package/{dist/index.d.ts → index.d.ts} +0 -0
- /package/{dist/src → src}/components/message-popup.d.ts +0 -0
- /package/{dist/src → src}/core/event-emitter.d.ts +0 -0
- /package/{dist/src → src}/core/service-worker-manager.d.ts +0 -0
- /package/{dist/src → src}/core/web-push-sdk.d.ts +0 -0
- /package/{dist/src → src}/index.d.ts +0 -0
- /package/{dist/src → src}/service-worker/sw.d.ts +0 -0
- /package/{dist/src → src}/types/index.d.ts +0 -0
- /package/{dist/src → src}/types/inner.d.ts +0 -0
- /package/{dist/src → src}/types/outer.d.ts +0 -0
- /package/{dist/src → src}/utils/browser-support.d.ts +0 -0
- /package/{dist/src → src}/utils/logger.d.ts +0 -0
- /package/{dist/src → src}/utils/storage.d.ts +0 -0
- /package/{dist/src → src}/utils/validator.d.ts +0 -0
- /package/{dist/sw.js → sw.js} +0 -0
package/src/core/web-push-sdk.ts
DELETED
|
@@ -1,690 +0,0 @@
|
|
|
1
|
-
import ChatSDK from '@tencentcloud/lite-chat/professional';
|
|
2
|
-
import { version } from '../../package.json';
|
|
3
|
-
import {
|
|
4
|
-
WebPushSDK as IWebPushSDK,
|
|
5
|
-
RegisterPushOptions,
|
|
6
|
-
EVENT,
|
|
7
|
-
NotificationMessage,
|
|
8
|
-
SubscriptionInfo,
|
|
9
|
-
MessageReceivedData,
|
|
10
|
-
} from '../types';
|
|
11
|
-
import { EventEmitter } from './event-emitter';
|
|
12
|
-
import { ServiceWorkerManager } from './service-worker-manager';
|
|
13
|
-
import { MessagePopup } from '../components/message-popup';
|
|
14
|
-
import { logger } from '../utils/logger';
|
|
15
|
-
import { Validator, ValidationError } from '../utils/validator';
|
|
16
|
-
import { browserSupport } from '../utils/browser-support';
|
|
17
|
-
import { genTestUserSig } from '../debug';
|
|
18
|
-
|
|
19
|
-
export class WebPushSDK implements IWebPushSDK {
|
|
20
|
-
static instance: WebPushSDK;
|
|
21
|
-
|
|
22
|
-
private eventEmitter: EventEmitter;
|
|
23
|
-
private serviceWorkerManager: ServiceWorkerManager;
|
|
24
|
-
private messagePopup: MessagePopup | null = null;
|
|
25
|
-
private isRegistered: boolean = false;
|
|
26
|
-
private registrationID: string = '';
|
|
27
|
-
private chat: any = null;
|
|
28
|
-
private SDKAppID: number = 0;
|
|
29
|
-
private appKey: string = '';
|
|
30
|
-
private vapidPublicKey: string = '';
|
|
31
|
-
private pendingMessages: any[] = []; // 存储页面隐藏时接收的消息
|
|
32
|
-
|
|
33
|
-
public EVENT = EVENT;
|
|
34
|
-
public VERSION: string = version;
|
|
35
|
-
|
|
36
|
-
constructor() {
|
|
37
|
-
logger.log('version:', version);
|
|
38
|
-
|
|
39
|
-
this.eventEmitter = new EventEmitter();
|
|
40
|
-
this.serviceWorkerManager = ServiceWorkerManager.getInstance(
|
|
41
|
-
this.eventEmitter
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
this.initializeBrowserCompatibility().catch((error) => {
|
|
45
|
-
logger.error('Browser compatibility initialization failed', error);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
this.setupInternalListeners();
|
|
49
|
-
this.setupVisibilityChangeListener();
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
static getInstance() {
|
|
53
|
-
if (!this.instance) {
|
|
54
|
-
this.instance = new WebPushSDK();
|
|
55
|
-
}
|
|
56
|
-
return this.instance;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async registerPush(options?: RegisterPushOptions) {
|
|
60
|
-
try {
|
|
61
|
-
Validator.validateRegisterPushOptions(options);
|
|
62
|
-
|
|
63
|
-
const { SDKAppID, appKey, userID, chat, logLevel } = options!;
|
|
64
|
-
|
|
65
|
-
// 设置主线程日志级别
|
|
66
|
-
if (logLevel !== undefined) {
|
|
67
|
-
logger.setLogLevel(logLevel);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (this.isRegistered) {
|
|
71
|
-
logger.warn(
|
|
72
|
-
'Push service already registered, will unregister first and then re-register'
|
|
73
|
-
);
|
|
74
|
-
await this.unRegisterPush();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
this.checkBrowserSupport();
|
|
78
|
-
|
|
79
|
-
const permission = await this.requestNotificationPermission();
|
|
80
|
-
if (permission !== 'granted') {
|
|
81
|
-
throw new Error('User denied notification permission');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
this.SDKAppID = SDKAppID;
|
|
85
|
-
this.appKey = appKey;
|
|
86
|
-
this.registrationID = userID;
|
|
87
|
-
|
|
88
|
-
// 初始化弹窗组件(使用默认配置)
|
|
89
|
-
this.messagePopup = new MessagePopup({
|
|
90
|
-
onClick: (message: any, _popup: HTMLElement) => {
|
|
91
|
-
this.serviceWorkerManager.postMessage({
|
|
92
|
-
type: 'REPORT_WEBPUSH_EVENT',
|
|
93
|
-
payload: {
|
|
94
|
-
...message,
|
|
95
|
-
eventType: 3,
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
if (chat) {
|
|
102
|
-
this.chat = chat;
|
|
103
|
-
} else {
|
|
104
|
-
// 检查 ChatSDK 是否可用
|
|
105
|
-
if (typeof ChatSDK === 'undefined') {
|
|
106
|
-
throw new Error(
|
|
107
|
-
'TencentCloudChat SDK is not available. Please ensure you have imported @tencentcloud/lite-chat/professional or provided a chat instance in the options.'
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
this.chat = ChatSDK.create({
|
|
112
|
-
SDKAppID: this.SDKAppID,
|
|
113
|
-
...options,
|
|
114
|
-
});
|
|
115
|
-
if (logLevel !== undefined) {
|
|
116
|
-
this.chat.setLogLevel(logLevel);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 启用在线推送监听
|
|
121
|
-
this.addChatListener();
|
|
122
|
-
|
|
123
|
-
await this.serviceWorkerManager.register();
|
|
124
|
-
|
|
125
|
-
// 向 Service Worker 发送日志级别配置
|
|
126
|
-
if (logLevel !== undefined) {
|
|
127
|
-
await this.sendLogLevelToServiceWorker(logLevel);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
await this.pushLogin();
|
|
131
|
-
|
|
132
|
-
const subscriptionInfo =
|
|
133
|
-
await this.serviceWorkerManager.getPushSubscription(
|
|
134
|
-
this.vapidPublicKey
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
await this.setToken(subscriptionInfo);
|
|
138
|
-
|
|
139
|
-
this.isRegistered = true;
|
|
140
|
-
|
|
141
|
-
logger.log('Push service registration successful', this.registrationID);
|
|
142
|
-
return this.registrationID;
|
|
143
|
-
} catch (error) {
|
|
144
|
-
if (error instanceof ValidationError) {
|
|
145
|
-
logger.error(
|
|
146
|
-
'Push service registration parameter validation failed',
|
|
147
|
-
error.message
|
|
148
|
-
);
|
|
149
|
-
throw error;
|
|
150
|
-
}
|
|
151
|
-
logger.error('Push service registration failed', error);
|
|
152
|
-
throw error;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
async unRegisterPush() {
|
|
157
|
-
try {
|
|
158
|
-
if (!this.isRegistered) {
|
|
159
|
-
logger.warn('Push service is not registered, no need to unregister');
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (this.chat) {
|
|
164
|
-
this.removeChatListener();
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// 销毁弹窗组件
|
|
168
|
-
if (this.messagePopup) {
|
|
169
|
-
this.messagePopup.destroy();
|
|
170
|
-
this.messagePopup = null;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
await this.serviceWorkerManager.unsubscribe();
|
|
174
|
-
await this.serviceWorkerManager.unregister();
|
|
175
|
-
await this.chat.callExperimentalAPI('logoutWebPush');
|
|
176
|
-
|
|
177
|
-
this.isRegistered = false;
|
|
178
|
-
this.registrationID = '';
|
|
179
|
-
this.SDKAppID = 0;
|
|
180
|
-
this.appKey = '';
|
|
181
|
-
this.vapidPublicKey = '';
|
|
182
|
-
this.clearState();
|
|
183
|
-
|
|
184
|
-
logger.log('Push service unregistration successful');
|
|
185
|
-
return true;
|
|
186
|
-
} catch (error) {
|
|
187
|
-
logger.error('Push service unregistration failed', error);
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
addPushListener(eventName: EVENT, listener: Function) {
|
|
193
|
-
try {
|
|
194
|
-
Validator.validateEventType(eventName);
|
|
195
|
-
Validator.validateListener(listener);
|
|
196
|
-
|
|
197
|
-
const listenerId = this.eventEmitter.on(eventName, listener);
|
|
198
|
-
logger.log('Add push listener', eventName, listenerId);
|
|
199
|
-
return listenerId;
|
|
200
|
-
} catch (error) {
|
|
201
|
-
if (error instanceof ValidationError) {
|
|
202
|
-
logger.error(
|
|
203
|
-
'Add push listener parameter validation failed',
|
|
204
|
-
error.message
|
|
205
|
-
);
|
|
206
|
-
throw error;
|
|
207
|
-
}
|
|
208
|
-
logger.error('Add push listener failed', error);
|
|
209
|
-
throw new Error('Add push listener failed');
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* 内部方法:向 Service Worker 发送日志级别配置
|
|
215
|
-
* @param logLevel 日志级别 0-4
|
|
216
|
-
*/
|
|
217
|
-
private async sendLogLevelToServiceWorker(
|
|
218
|
-
logLevel: 0 | 1 | 2 | 3 | 4
|
|
219
|
-
): Promise<void> {
|
|
220
|
-
try {
|
|
221
|
-
if (this.serviceWorkerManager) {
|
|
222
|
-
await this.serviceWorkerManager.postMessage({
|
|
223
|
-
type: 'SET_LOG_LEVEL',
|
|
224
|
-
payload: { logLevel },
|
|
225
|
-
});
|
|
226
|
-
logger.log('Log level sent to Service Worker:', logLevel);
|
|
227
|
-
}
|
|
228
|
-
} catch (error) {
|
|
229
|
-
logger.warn('Failed to send log level to Service Worker', error);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
removePushListener(eventName: EVENT, listener: Function) {
|
|
234
|
-
try {
|
|
235
|
-
Validator.validateEventType(eventName);
|
|
236
|
-
Validator.validateListener(listener);
|
|
237
|
-
|
|
238
|
-
const result = this.eventEmitter.off(eventName, listener);
|
|
239
|
-
logger.log('Remove push listener', eventName, result);
|
|
240
|
-
return result;
|
|
241
|
-
} catch (error) {
|
|
242
|
-
if (error instanceof ValidationError) {
|
|
243
|
-
logger.error(
|
|
244
|
-
'Remove push listener parameter validation failed',
|
|
245
|
-
error.message
|
|
246
|
-
);
|
|
247
|
-
throw error;
|
|
248
|
-
}
|
|
249
|
-
logger.error('Remove push listener failed', error);
|
|
250
|
-
return false;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private checkBrowserSupport(): void {
|
|
255
|
-
if (!browserSupport.checkBrowserSupport()) {
|
|
256
|
-
const capability = browserSupport.detectWebPushCapability();
|
|
257
|
-
throw new Error(`Browser not supported: ${capability.reason}`);
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private async requestNotificationPermission(): Promise<NotificationPermission> {
|
|
262
|
-
try {
|
|
263
|
-
const currentPermission =
|
|
264
|
-
await browserSupport.checkNotificationPermission();
|
|
265
|
-
|
|
266
|
-
if (currentPermission === 'granted') {
|
|
267
|
-
return 'granted';
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const capability = browserSupport.detectWebPushCapability();
|
|
271
|
-
const browserName = capability.browserName;
|
|
272
|
-
|
|
273
|
-
if (currentPermission === 'denied') {
|
|
274
|
-
const errorMessage = `Notification permission denied for ${browserName}. Please check your system settings.`;
|
|
275
|
-
throw new Error(errorMessage);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
if (browserSupport.requiresUserGesture()) {
|
|
279
|
-
logger.warn(
|
|
280
|
-
'Current browser requires user gesture to request notification permission'
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const permission = await browserSupport.requestNotificationPermission();
|
|
285
|
-
|
|
286
|
-
if (permission !== 'granted') {
|
|
287
|
-
throw new Error(
|
|
288
|
-
`User denied notification permission for ${browserName}`
|
|
289
|
-
);
|
|
290
|
-
} else {
|
|
291
|
-
this.showSystemPermissionAlert(browserName);
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return permission;
|
|
295
|
-
} catch (error) {
|
|
296
|
-
logger.error('Request notification permission failed', error);
|
|
297
|
-
throw error;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private async pushLogin(): Promise<void> {
|
|
302
|
-
try {
|
|
303
|
-
const res = await this.chat.callExperimentalAPI('loginWebPush', {
|
|
304
|
-
userID: this.registrationID,
|
|
305
|
-
userSig: genTestUserSig({
|
|
306
|
-
SDKAppID: this.SDKAppID,
|
|
307
|
-
secretKey: this.appKey,
|
|
308
|
-
userID: this.registrationID,
|
|
309
|
-
}).userSig,
|
|
310
|
-
pushSDKVersion: version,
|
|
311
|
-
});
|
|
312
|
-
if (res.data.vapid) {
|
|
313
|
-
this.vapidPublicKey = res.data.vapid;
|
|
314
|
-
} else {
|
|
315
|
-
logger.error('Failed to fetch VAPID public key');
|
|
316
|
-
}
|
|
317
|
-
} catch (error: any) {
|
|
318
|
-
// 处理专业版功能错误码
|
|
319
|
-
if (error?.code === 2905) {
|
|
320
|
-
const modifiedError = new Error(
|
|
321
|
-
'The API you are calling is only available in the Professional edition. Please change your import from @tencentcloud/lite-chat to @tencentcloud/lite-chat/professional to access this feature.'
|
|
322
|
-
);
|
|
323
|
-
(modifiedError as any).code = error.code;
|
|
324
|
-
throw modifiedError;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
throw error;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
private onMessageReceived(event: any) {
|
|
332
|
-
logger.log('Received message data', event);
|
|
333
|
-
event.data.forEach((item: any) => {
|
|
334
|
-
const { webPush } = item;
|
|
335
|
-
const { content, info, extension } = webPush as MessageReceivedData;
|
|
336
|
-
const { TaskId, WebpushReportUrl, OnlineClickExt } = extension;
|
|
337
|
-
|
|
338
|
-
if (content) {
|
|
339
|
-
try {
|
|
340
|
-
const message = JSON.parse(content);
|
|
341
|
-
const outerMessage = {
|
|
342
|
-
id: TaskId,
|
|
343
|
-
...message,
|
|
344
|
-
};
|
|
345
|
-
this.eventEmitter.emit(EVENT.MESSAGE_RECEIVED, outerMessage);
|
|
346
|
-
|
|
347
|
-
if (message.MsgType !== 'custom') {
|
|
348
|
-
const messageWithExtras = {
|
|
349
|
-
...outerMessage,
|
|
350
|
-
rptURL: WebpushReportUrl,
|
|
351
|
-
rptExt: OnlineClickExt,
|
|
352
|
-
};
|
|
353
|
-
|
|
354
|
-
if (this.messagePopup && document.visibilityState === 'visible') {
|
|
355
|
-
this.messagePopup.show(messageWithExtras);
|
|
356
|
-
} else if (this.messagePopup) {
|
|
357
|
-
this.pendingMessages.push(messageWithExtras);
|
|
358
|
-
logger.log(
|
|
359
|
-
'Message added to pending queue (page hidden)',
|
|
360
|
-
TaskId
|
|
361
|
-
);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
} catch (error) {
|
|
365
|
-
logger.error('Failed to parse message content', error);
|
|
366
|
-
this.eventEmitter.emit(EVENT.MESSAGE_RECEIVED, content);
|
|
367
|
-
}
|
|
368
|
-
} else if (info) {
|
|
369
|
-
try {
|
|
370
|
-
this.sendNotificationToServiceWorker({
|
|
371
|
-
id: TaskId,
|
|
372
|
-
...info,
|
|
373
|
-
rptURL: WebpushReportUrl,
|
|
374
|
-
rptExt: OnlineClickExt,
|
|
375
|
-
});
|
|
376
|
-
} catch (error) {
|
|
377
|
-
logger.error('Failed to send notification to service worker', error);
|
|
378
|
-
this.eventEmitter.emit(EVENT.MESSAGE_RECEIVED, info);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
});
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private onMessageRevoked(event: any) {
|
|
385
|
-
event.data.forEach((item: { ID: any }) => {
|
|
386
|
-
const { ID } = item;
|
|
387
|
-
logger.log('Message revoked', ID);
|
|
388
|
-
|
|
389
|
-
const isPopupMessage =
|
|
390
|
-
this.messagePopup && this.messagePopup.hasMessage(ID);
|
|
391
|
-
|
|
392
|
-
// 检查待处理消息队列中是否有该消息
|
|
393
|
-
const pendingIndex = this.pendingMessages.findIndex(
|
|
394
|
-
(msg) => msg.id === ID
|
|
395
|
-
);
|
|
396
|
-
if (pendingIndex !== -1) {
|
|
397
|
-
this.pendingMessages.splice(pendingIndex, 1);
|
|
398
|
-
logger.log('Pending message removed from queue', ID);
|
|
399
|
-
|
|
400
|
-
this.eventEmitter.emit(EVENT.MESSAGE_REVOKED, {
|
|
401
|
-
messageID: ID,
|
|
402
|
-
});
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
if (isPopupMessage) {
|
|
407
|
-
this.messagePopup!.close(ID);
|
|
408
|
-
|
|
409
|
-
this.eventEmitter.emit(EVENT.MESSAGE_REVOKED, {
|
|
410
|
-
messageID: ID,
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
logger.log('Popup message revoked and closed', ID);
|
|
414
|
-
} else {
|
|
415
|
-
try {
|
|
416
|
-
this.sendMessageRevokeToServiceWorker(ID);
|
|
417
|
-
logger.log('Message revoke sent to service worker', ID);
|
|
418
|
-
} catch (error) {
|
|
419
|
-
logger.error(
|
|
420
|
-
'Failed to send message revoke to service worker',
|
|
421
|
-
error
|
|
422
|
-
);
|
|
423
|
-
this.eventEmitter.emit(EVENT.MESSAGE_REVOKED, {
|
|
424
|
-
messageID: ID,
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
private addChatListener() {
|
|
432
|
-
if (!this.chat) return;
|
|
433
|
-
|
|
434
|
-
try {
|
|
435
|
-
this.chat.on(
|
|
436
|
-
ChatSDK.EVENT.WEB_PUSH_MESSAGE_RECEIVED,
|
|
437
|
-
this.onMessageReceived.bind(this)
|
|
438
|
-
);
|
|
439
|
-
this.chat.on(
|
|
440
|
-
ChatSDK.EVENT.MESSAGE_REVOKED,
|
|
441
|
-
this.onMessageRevoked.bind(this)
|
|
442
|
-
);
|
|
443
|
-
logger.log('Chat listeners added for online push');
|
|
444
|
-
} catch (error) {
|
|
445
|
-
logger.error('Failed to add chat listeners', error);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
private removeChatListener() {
|
|
450
|
-
if (!this.chat) return;
|
|
451
|
-
|
|
452
|
-
try {
|
|
453
|
-
this.chat.off(
|
|
454
|
-
ChatSDK.EVENT.WEB_PUSH_MESSAGE_RECEIVED,
|
|
455
|
-
this.onMessageReceived
|
|
456
|
-
);
|
|
457
|
-
this.chat.off(ChatSDK.EVENT.MESSAGE_REVOKED, this.onMessageRevoked);
|
|
458
|
-
logger.log('Chat listeners removed');
|
|
459
|
-
} catch (error) {
|
|
460
|
-
logger.error('Failed to remove chat listeners', error);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
private getSystemPermissionMessage(browserName: string): string {
|
|
465
|
-
const userAgent = navigator.userAgent.toLowerCase();
|
|
466
|
-
const isMacOS = userAgent.includes('mac os');
|
|
467
|
-
|
|
468
|
-
if (isMacOS) {
|
|
469
|
-
return `For macOS: System Preferences > Notifications > ${browserName}, then enable notifications.`;
|
|
470
|
-
} else {
|
|
471
|
-
return `For Windows: Settings > Privacy > Notifications > ${browserName}, then enable notifications.`;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
private showSystemPermissionAlert(browserName: string): void {
|
|
476
|
-
const message = this.getSystemPermissionMessage(browserName);
|
|
477
|
-
const fullMessage = `System notification permission for ${browserName}.\n\n${message}\n\nPlease refresh the page after enabling notifications.`;
|
|
478
|
-
alert(fullMessage);
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
private getBrowserInstType(): number {
|
|
482
|
-
const userAgent = navigator.userAgent.toLowerCase();
|
|
483
|
-
|
|
484
|
-
if (userAgent.includes('edg/')) {
|
|
485
|
-
return 3003; // Edge
|
|
486
|
-
} else if (userAgent.includes('yabrowser/')) {
|
|
487
|
-
return 3004; // Yandex
|
|
488
|
-
} else if (userAgent.includes('opr/') || userAgent.includes('opera/')) {
|
|
489
|
-
return 3005; // Opera
|
|
490
|
-
} else if (userAgent.includes('chrome/') && !userAgent.includes('edg/')) {
|
|
491
|
-
return 3000; // Chrome
|
|
492
|
-
} else if (userAgent.includes('firefox/')) {
|
|
493
|
-
return 3001; // Firefox
|
|
494
|
-
} else if (
|
|
495
|
-
userAgent.includes('safari/') &&
|
|
496
|
-
!userAgent.includes('chrome/')
|
|
497
|
-
) {
|
|
498
|
-
return 3002; // Safari
|
|
499
|
-
} else {
|
|
500
|
-
return 1; // 未知或其他浏览器
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
private async setToken(subscriptionInfo: SubscriptionInfo): Promise<void> {
|
|
505
|
-
try {
|
|
506
|
-
const capability = browserSupport.detectWebPushCapability();
|
|
507
|
-
const browserInfo = browserSupport.getBrowserInfo();
|
|
508
|
-
|
|
509
|
-
const data = {
|
|
510
|
-
browserType: this.getBrowserInstType(),
|
|
511
|
-
pushToken: subscriptionInfo.endpoint,
|
|
512
|
-
webPushAuthKey: subscriptionInfo.auth,
|
|
513
|
-
webPushP256: subscriptionInfo.p256dh,
|
|
514
|
-
pushSDKVersion: version,
|
|
515
|
-
browserVersion: `${capability.browserName} ${capability.browserVersion}`,
|
|
516
|
-
browserPlatform: browserInfo.platform,
|
|
517
|
-
browserLanguage: browserInfo.language,
|
|
518
|
-
};
|
|
519
|
-
|
|
520
|
-
const res = await this.chat.callExperimentalAPI('setWebPushToken', data);
|
|
521
|
-
return res;
|
|
522
|
-
} catch (error) {
|
|
523
|
-
logger.error('Device registration failed', error);
|
|
524
|
-
throw error;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
private async initializeBrowserCompatibility(): Promise<void> {
|
|
529
|
-
try {
|
|
530
|
-
const capability = browserSupport.detectWebPushCapability();
|
|
531
|
-
|
|
532
|
-
logger.log('Browser Web Push capability initialization completed', {
|
|
533
|
-
browser: `${capability.browserName} ${capability.browserVersion}`,
|
|
534
|
-
supported: capability.supported,
|
|
535
|
-
reason: capability.reason,
|
|
536
|
-
});
|
|
537
|
-
|
|
538
|
-
if (!capability.supported) {
|
|
539
|
-
logger.warn(
|
|
540
|
-
'[WebPush] Browser compatibility warning:',
|
|
541
|
-
capability.reason
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
|
-
} catch (error) {
|
|
545
|
-
logger.error('Browser compatibility initialization failed', error);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
private setupVisibilityChangeListener(): void {
|
|
550
|
-
document.addEventListener('visibilitychange', () => {
|
|
551
|
-
if (
|
|
552
|
-
document.visibilityState === 'visible' &&
|
|
553
|
-
this.messagePopup &&
|
|
554
|
-
this.pendingMessages.length > 0
|
|
555
|
-
) {
|
|
556
|
-
logger.log(
|
|
557
|
-
'Page became visible, showing pending messages',
|
|
558
|
-
this.pendingMessages.length
|
|
559
|
-
);
|
|
560
|
-
|
|
561
|
-
const messagesToShow = [...this.pendingMessages];
|
|
562
|
-
this.pendingMessages = [];
|
|
563
|
-
|
|
564
|
-
const timeout = setTimeout(() => {
|
|
565
|
-
messagesToShow.forEach((message) => {
|
|
566
|
-
if (this.messagePopup) {
|
|
567
|
-
this.messagePopup.show(message);
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
clearTimeout(timeout);
|
|
571
|
-
}, 100);
|
|
572
|
-
}
|
|
573
|
-
});
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
private setupInternalListeners(): void {
|
|
577
|
-
this.eventEmitter.on(
|
|
578
|
-
EVENT.MESSAGE_RECEIVED,
|
|
579
|
-
(message: NotificationMessage) => {
|
|
580
|
-
if (this.SDKAppID && this.registrationID && message.messageID) {
|
|
581
|
-
this.pushStatistics(message.messageID, 'reach');
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
);
|
|
585
|
-
|
|
586
|
-
this.eventEmitter.on(EVENT.NOTIFICATION_CLICKED, (data: any) => {
|
|
587
|
-
if (
|
|
588
|
-
this.SDKAppID &&
|
|
589
|
-
this.registrationID &&
|
|
590
|
-
data.notification?.messageID
|
|
591
|
-
) {
|
|
592
|
-
this.pushStatistics(data.notification.messageID, 'click');
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
private async pushStatistics(messageID: string, type: string): Promise<void> {
|
|
598
|
-
try {
|
|
599
|
-
if (!this.chat) {
|
|
600
|
-
logger.warn('Chat SDK not initialized, skipping statistics');
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
const data = {
|
|
605
|
-
messageID,
|
|
606
|
-
type,
|
|
607
|
-
timestamp: Date.now(),
|
|
608
|
-
SDKAppID: this.SDKAppID,
|
|
609
|
-
registrationID: this.registrationID,
|
|
610
|
-
};
|
|
611
|
-
|
|
612
|
-
// Call chat pushStatistics API
|
|
613
|
-
// await this.chat.callExperimentalAPI('pushStatistics', data);
|
|
614
|
-
logger.log(`Message ${type} statistics recorded`, data);
|
|
615
|
-
} catch (error) {
|
|
616
|
-
logger.error(`Failed to record message ${type} statistics`, error);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
private clearState(): void {
|
|
621
|
-
this.isRegistered = false;
|
|
622
|
-
this.registrationID = '';
|
|
623
|
-
this.SDKAppID = 0;
|
|
624
|
-
this.appKey = '';
|
|
625
|
-
this.vapidPublicKey = '';
|
|
626
|
-
this.pendingMessages = []; // 清空待处理消息
|
|
627
|
-
|
|
628
|
-
// 清理弹窗
|
|
629
|
-
if (this.messagePopup) {
|
|
630
|
-
this.messagePopup.destroy();
|
|
631
|
-
this.messagePopup = null;
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/**
|
|
636
|
-
* 向 service-worker 发送通知消息,使用与 push 事件相同的数据格式
|
|
637
|
-
* @param webpushInfo webpush 信息,格式与 SW push 事件接收的数据一致
|
|
638
|
-
*/
|
|
639
|
-
private async sendNotificationToServiceWorker(
|
|
640
|
-
webpushInfo: any
|
|
641
|
-
): Promise<void> {
|
|
642
|
-
try {
|
|
643
|
-
if (!this.serviceWorkerManager) {
|
|
644
|
-
logger.warn(
|
|
645
|
-
'ServiceWorkerManager not initialized, skipping notification'
|
|
646
|
-
);
|
|
647
|
-
return;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
// 直接使用 webpushInfo 数据,格式与 push 事件一致
|
|
651
|
-
// 通过 ServiceWorkerManager 向 service-worker 发送消息,模拟 push 事件的处理
|
|
652
|
-
await this.serviceWorkerManager.postMessage({
|
|
653
|
-
type: 'PROCESS_WEBPUSH_DATA',
|
|
654
|
-
payload: webpushInfo,
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
logger.log('WebPush data sent to service-worker', webpushInfo);
|
|
658
|
-
} catch (error) {
|
|
659
|
-
logger.error('Failed to send webpush data to service-worker', error);
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* 向 service-worker 发送消息撤回请求
|
|
665
|
-
* @param messageID 要撤回的消息ID
|
|
666
|
-
*/
|
|
667
|
-
private async sendMessageRevokeToServiceWorker(
|
|
668
|
-
messageID: string
|
|
669
|
-
): Promise<void> {
|
|
670
|
-
try {
|
|
671
|
-
if (!this.serviceWorkerManager) {
|
|
672
|
-
logger.warn(
|
|
673
|
-
'ServiceWorkerManager not initialized, skipping message revoke'
|
|
674
|
-
);
|
|
675
|
-
return;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
// 向 ServiceWorker 发送消息撤回请求
|
|
679
|
-
await this.serviceWorkerManager.postMessage({
|
|
680
|
-
type: 'REVOKE_MESSAGE',
|
|
681
|
-
payload: { messageID },
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
logger.log('Message revoke sent to service-worker', messageID);
|
|
685
|
-
} catch (error) {
|
|
686
|
-
logger.error('Failed to send message revoke to service-worker', error);
|
|
687
|
-
throw error;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
}
|