@gogoqiu/tencent-http-server 0.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.
Files changed (84) hide show
  1. package/bin/gogoqiu-node-http-service +3 -0
  2. package/dist/build.d.ts +3 -0
  3. package/dist/build.d.ts.map +1 -0
  4. package/dist/build.js +3 -0
  5. package/dist/build.js.map +1 -0
  6. package/dist/routes/index.d.ts +4 -0
  7. package/dist/routes/index.d.ts.map +1 -0
  8. package/dist/routes/index.js +106 -0
  9. package/dist/routes/index.js.map +1 -0
  10. package/dist/server.d.ts +3 -0
  11. package/dist/server.d.ts.map +1 -0
  12. package/dist/server.js +318 -0
  13. package/dist/server.js.map +1 -0
  14. package/dist/serverCmd.d.ts +8 -0
  15. package/dist/serverCmd.d.ts.map +1 -0
  16. package/dist/serverCmd.js +44 -0
  17. package/dist/serverCmd.js.map +1 -0
  18. package/dist/shell/install.d.ts +5 -0
  19. package/dist/shell/install.d.ts.map +1 -0
  20. package/dist/shell/install.js +221 -0
  21. package/dist/shell/install.js.map +1 -0
  22. package/dist/shell/postinst.d.ts +2 -0
  23. package/dist/shell/postinst.d.ts.map +1 -0
  24. package/dist/shell/postinst.js +4 -0
  25. package/dist/shell/postinst.js.map +1 -0
  26. package/dist/shell.d.ts +2 -0
  27. package/dist/shell.d.ts.map +1 -0
  28. package/dist/shell.js +249 -0
  29. package/dist/shell.js.map +1 -0
  30. package/package.json +82 -0
  31. package/public/captures1//346/265/213/350/257/225/347/224/250captures +0 -0
  32. package/public/chat/offer.html +796 -0
  33. package/public/chat/options.html +58 -0
  34. package/public/chat/server-info.html +32 -0
  35. package/public/chat2.html +272 -0
  36. package/public/chat3.html +246 -0
  37. package/public/chat4.html +302 -0
  38. package/public/chat5.html +41 -0
  39. package/public/formdata.html +41 -0
  40. package/public/hls-player.htm +41 -0
  41. package/public/img/back.svg +1 -0
  42. package/public/img/offline.svg +1 -0
  43. package/public/img/online.svg +1 -0
  44. package/public/ip-record.html +28 -0
  45. package/public/js/encrypt.js +36 -0
  46. package/public/js/socket.io.min.js +7 -0
  47. package/public/js/socket.io.min.js.map +1 -0
  48. package/public/myhost/hostReg.html +35 -0
  49. package/public/mylog-chat.htm +260 -0
  50. package/public/mylog3.html +245 -0
  51. package/public/navbar.css +17 -0
  52. package/public/readme.txt +3 -0
  53. package/public/scroll.htm +139 -0
  54. package/public/ssh-client.html +0 -0
  55. package/public/upload-file.html +226 -0
  56. package/public/upload.html +23 -0
  57. package/public/uploads1/files-1757866537383-447469495.jpg +0 -0
  58. package/public/uploads1/files-1757867389485-764531720.jpg +0 -0
  59. package/public/uploads1/files-1757867518311-278635302.jpg +0 -0
  60. package/public/uploads1/files-1757867629687-688924576.jpg +0 -0
  61. package/public/uploads1/files-1757868630683-52261917.jpg +0 -0
  62. package/public/uploads1/files-1757869187061-619427683.jpg +0 -0
  63. package/public/uploads1/small_files-1757869187061-619427683.jpg +0 -0
  64. package/public/uploads1//346/265/213/350/257/225/347/224/250upload +0 -0
  65. package/public/utils.html +57 -0
  66. package/public/utils.js +161 -0
  67. package/public/webrtc/rtc-client.html +238 -0
  68. package/public/webrtc/rtc-file-transfer-client.html +238 -0
  69. package/public/webrtc/rtc-file-transfer-server.html +453 -0
  70. package/public/webrtc/rtc-server.html +453 -0
  71. package/public/webrtc/video-client-input.html +264 -0
  72. package/public/webrtc/video-server-input.html +312 -0
  73. package/public/webrtc/webrtc-chat-with-files.html +581 -0
  74. package/public/webrtc/webrtc-chat.html +367 -0
  75. package/public/webrtc/webrtc-file-offer.html +88 -0
  76. package/public/webrtc/webrtc1.html +186 -0
  77. package/readme.txt +71 -0
  78. package/views/chat.ejs +53 -0
  79. package/views/index.ejs +125 -0
  80. package/views/special-message.ejs +8 -0
  81. package/views/ssh.ejs +142 -0
  82. package/views/utils.ejs +0 -0
  83. package/views/webrtc/client.ejs +203 -0
  84. package/views/webrtc/server.ejs +365 -0
@@ -0,0 +1,796 @@
1
+ <!--
2
+ 用户选择
3
+ 加密密钥编辑
4
+ -->
5
+ <title>配置</title>
6
+ <script src="https://cdn.tailwindcss.com"></script>
7
+ <!--/dist/public-->
8
+ <script src="/js/socket.io.min.js"></script>
9
+ <script src="/js/encrypt.js"></script>
10
+ <script src="/utils.js"></script>
11
+ <!--/dist/web-->
12
+ <style>
13
+ body {
14
+ font-family: Arial, sans-serif;
15
+ }
16
+
17
+ #messages {
18
+ list-style-type: none;
19
+ height: 30%;
20
+ overflow-y: auto;
21
+ border: 2px solid #e2e8f0;
22
+ border-radius: 6px;
23
+ transition: border-color 0.3s ease;
24
+ }
25
+
26
+ #messages1 li {
27
+ padding: 8px;
28
+ margin-bottom: 2px;
29
+ background-color: #f3f3f3;
30
+ }
31
+
32
+ #form {
33
+ background: #000;
34
+ padding: 3px;
35
+ position: fixed;
36
+ bottom: 0;
37
+ width: 100%;
38
+ }
39
+
40
+ #input {
41
+ border: none;
42
+ padding: 10px;
43
+ width: 90%;
44
+ margin-right: .5%;
45
+ }
46
+
47
+ #send {
48
+ width: 9%;
49
+ }
50
+
51
+ input[type="button"],
52
+ button {
53
+ background: rgb(130, 224, 170);
54
+ border: none;
55
+ padding: 10px;
56
+ }
57
+
58
+ .row {
59
+ display: flex;
60
+ flex-direction: row;
61
+ }
62
+
63
+ div .created {
64
+ padding-left: 10px;
65
+ }
66
+
67
+ div .from {
68
+ padding-left: 10px;
69
+ }
70
+
71
+ #encKey {
72
+ background-color: gray;
73
+ }
74
+
75
+ video {
76
+ height: 300px;
77
+ width: 400px;
78
+ }
79
+
80
+ </style>
81
+ <link rel="stylesheet" href="/navbar.css">
82
+ <div>
83
+ <!--做一个时钟,看载入的倒计时, 刷新数据的倒计时-->
84
+ <div class="row">
85
+ <a href="/"><img id="icon" class="back imgbutton"></img></a>
86
+ <a href="/chat/options.html"><img id="icon" class="options imgbutton"></img></a>
87
+ <img id="status" class="imgbutton offline" />
88
+ <label>from</label>
89
+ <!--不准编辑,只能到options.html修改-->
90
+ <select id="from" disabled></select>
91
+ <label>to</label>
92
+ <select id="to"></select>
93
+ <!--可以做成浏览器“密码保存”的?-->
94
+ <label>加密字符</label>
95
+ <!--不准编辑,只能到options.html修改-->
96
+ <input disabled id="encKey" />
97
+ <label>轮巡时间</label>
98
+ <input id="interval" disabled />
99
+ <select>
100
+ <option value="offer">offer</option>
101
+ <option value="answer">answer</option>
102
+ </select>
103
+ <!--发起p2p请求-->
104
+ <button id="createChannel" onclick="createOffer()"
105
+ class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">建立信道</button>
106
+ <input type="file" id="fileInput" />
107
+ <button onclick="sendFile()"
108
+ class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">发文件</button>
109
+ <button onclick="closeChannel()"
110
+ class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">关闭ws</button>
111
+ </div>
112
+ <ul id="messages" class="scrollable-list p-3 space-y-2"></ul>
113
+ <form id="form" action="">
114
+ <input id="input" autocomplete="off" />
115
+ <button id="send"
116
+ class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors">Send</button>
117
+ </form>
118
+ <!--
119
+ 视频传送
120
+ 传送文件
121
+ -->
122
+ <video id="sendVideo" controls muted crossorigin="anonymous">
123
+ <!-- NB CORS: https://bugzilla.mozilla.org/show_bug.cgi?id=1177793 -->
124
+ <source src="http://git.my.host:28888/200_pages1/mnt/disk2/sd2.3.2.mp4" type="video/mp4" />
125
+ <source src="http://git.my.host:28888/200_pages1/mnt/disk2/sd2.3.1.mp4" type="video/mp4" />
126
+ <p>This browser does not support the video element.</p>
127
+ </video>
128
+ <video id="getVideo" autoplay controls></video>
129
+ </div>
130
+ <script>
131
+ /*
132
+ 填充"to"
133
+ */
134
+ let firstId = -1
135
+ let timer = null
136
+ var video, stream
137
+ var startTime;
138
+ let socket
139
+ let pc, dataChannel
140
+
141
+ const from = document.getElementById('from');
142
+ const to = document.getElementById('to');
143
+ const input = document.getElementById('input');
144
+ const messages = document.getElementById('messages');
145
+ const status = document.getElementById("status")
146
+ //const encKey = document.getElementById('encKey')
147
+
148
+ const form = document.getElementById('form');
149
+ const sendVideo = document.getElementById('sendVideo');
150
+ const getVideo = document.getElementById('getVideo');
151
+
152
+ window.onload = function () {
153
+ // should input offer/answer/candidate
154
+ // will "output" offer/answer/candidate
155
+ initWsProxy()
156
+ // set video callback, get video stream will bind with offer/answer
157
+ initVideo()
158
+ initPC()
159
+ }
160
+
161
+ function initWsProxy() {
162
+ socket = io();
163
+
164
+ socket.on('chat login', function (resps) {
165
+
166
+ if( resps.length > 0 && resps[resps.length-1].retVal ){
167
+ console.log( "socket id", resps[resps.length-1].data )
168
+ }else
169
+ console.log("login response", resps)
170
+ });
171
+ /*
172
+ "'l;llljd", 发送异常了
173
+ 消息还是通过中转服务器发送的,而不是点对点
174
+ */
175
+ socket.on('chat message', function (resps) {
176
+ // resps自动解析为对象的
177
+ if (resps[resps.length - 1].retVal) {
178
+ const { command, results } = resps[resps.length - 1].data
179
+ if (command === "set") {
180
+ // 如果是sendMessage立即收到的chat message;
181
+ // set的反馈只反馈自身那条
182
+ if (results[results.length - 1].id === firstId + 1) {
183
+
184
+ } else {
185
+
186
+ }
187
+ // 取消已预订的查询,马上查询
188
+ nextQuery(1)
189
+ } else if (command === "get") {
190
+ results.forEach((row) => {
191
+ const item = document.createElement('li');
192
+ item.className = ('p-3 bg-blue-50 rounded-md border border-blue-100');
193
+ item.innerHTML = buildItem(row);
194
+ //messages.insertBefore(item, document.querySelector("li"));
195
+ messages.appendChild(item);
196
+ firstId = row.id;
197
+ })
198
+ // 比较规则的每15000请求一次
199
+ nextQuery()
200
+ }
201
+ } else {
202
+ console.log(resps[resps.length - 1].msg)
203
+ }
204
+
205
+ //console.log(msg);
206
+ return
207
+ });
208
+
209
+ socket.on('chat offer', function (msg) {
210
+ if ("length" in msg && msg.length > 0) {
211
+ let resps = msg
212
+ console.log("send offer response", resps)
213
+ } else if ("offer" in msg) {
214
+ console.log('got offer', "from", msg.from, msg.offer)
215
+ setOffer(msg.offer)
216
+ } else {
217
+ console.log('error offer msg', msg);
218
+ }
219
+ })
220
+
221
+ socket.on('chat answer', function (msg) {
222
+ if ("length" in msg && msg.length > 0) {
223
+ let resps = msg
224
+ console.log("send offer response", resps)
225
+ } else if ("answer" in msg) {
226
+ console.log('got answer', "from", msg.from, msg.answer)
227
+ setAnswer(msg.answer)
228
+ } else {
229
+ console.log('error offer msg', msg);
230
+ }
231
+ })
232
+
233
+ socket.on('chat ice-candidate', function (msg) {
234
+ if ("length" in msg && msg.length > 0) {
235
+ let resps = msg
236
+ console.log("send candidate response", resps)
237
+ } else if ("candidate" in msg) {
238
+ console.log('got candidate', "from", msg.from, msg.candidate)
239
+ setCandidate(msg.candidate)
240
+ } else {
241
+ console.log('error offer msg', msg);
242
+ }
243
+ })
244
+
245
+ socket.on('connect', function () {
246
+ //socket.emit('add user', username);
247
+ //status.src = "/img/online.svg"
248
+ status.classList.remove('offline')
249
+ status.classList.add('online');
250
+ // 改写为ws获取信息
251
+ fillselects(true).then(() => {
252
+ console.log("获取信息成功")
253
+ loadprev(socket)
254
+ socket.emit("chat login", { hostname: getFromValue() })
255
+ }).catch((e) => {
256
+ console.log("获取信息失败", e)
257
+ })
258
+ });
259
+
260
+ socket.on('disconnect', function () {
261
+ //log('you have been disconnected');
262
+ //status.src = "/img/offline.svg"
263
+ status.classList.remove('online')
264
+ status.classList.add('offline');
265
+ if (timer)
266
+ clearInterval(timer)
267
+ });
268
+
269
+ }
270
+
271
+ function initPC() {
272
+ const configuration = {
273
+ iceServers: [
274
+ //stun协议服务器。反馈外网信息
275
+ { urls: 'stun:stun.l.google.com:19302' },
276
+ /*
277
+ {
278
+ urls: 'turn:yourturnserver.com:3478',
279
+ username: 'yourusername',
280
+ credential: 'yourpassword'
281
+ }*/
282
+ ]
283
+ };
284
+ // 获得外网信息?
285
+ // 如何重新请求candidate
286
+ pc = new RTCPeerConnection(configuration);
287
+ pc.onicecandidate = function (e) {
288
+ // 与ice通讯的结果,从ice服务器获得的自身外网情况
289
+ //console.log('onicecandidate', event);
290
+ //document.getElementById("candidate").value = event.candidate;
291
+ //socket.emit('chat ice-candidate', { host: hostId, candidate: event.candidate });
292
+
293
+ //cds.push(e.candidate)
294
+ //document.getElementById('candidate').value = JSON.stringify(cds, null, 2);
295
+ sendCandidate(e.candidate)
296
+ };
297
+ pc.oniceconnectionstatechange = function (e) {
298
+ const currentState = e.target.iceConnectionState;
299
+ console.log('ICE 连接状态变化:', currentState);
300
+
301
+ // 根据不同状态处理
302
+ switch (currentState) {
303
+ case 'new':
304
+ // 初始状态:连接尚未开始建立
305
+ console.log('ICE 连接已创建,尚未开始连接');
306
+ break;
307
+ case 'checking':
308
+ // 正在检查连接:ICE 正在尝试不同的候选地址(如本地地址、STUN 反射地址、TURN 中继地址)
309
+ console.log('ICE 正在检查连接候选地址...');
310
+ break;
311
+ case 'connected':
312
+ // 部分连接成功:至少有一个 P2P 连接已建立(可能仍在检查其他候选以优化连接)
313
+ console.log('ICE 已建立部分连接,通信可正常进行');
314
+ break;
315
+ case 'completed':
316
+ // 完全连接成功:所有 ICE 候选检查完成,最佳 P2P 连接已确定
317
+ console.log('ICE 连接完全建立,已确定最佳通信路径');
318
+ break;
319
+ case 'failed':
320
+ // 连接失败:所有 ICE 候选检查都失败,P2P 连接无法建立(需考虑用 TURN 中继或提示用户)
321
+ console.error('ICE 连接失败,无法建立 P2P 通信');
322
+ // 可选:触发重试逻辑或提示用户
323
+ retryConnection();
324
+ break;
325
+ case 'disconnected':
326
+ // 连接断开:之前建立的连接丢失(如网络波动、对方离线)
327
+ console.warn('ICE 连接已断开,尝试重连...');
328
+ // 可选:尝试重新收集 ICE 候选并重连
329
+ //peerConnection.restartIce();
330
+ console.error('ICE 连接失败,尝试重启...');
331
+ // 重启 ICE 流程(会重新收集候选并尝试连接)
332
+ pc.restartIce()
333
+ .then(() => console.log('ICE 重启成功,正在重新连接...'))
334
+ .catch(err => console.error('ICE 重启失败:', err));
335
+ break;
336
+ case 'closed':
337
+ // 连接已关闭:主动调用 peerConnection.close() 后触发
338
+ console.log('ICE 连接已主动关闭');
339
+ // 可选:释放资源、清理 UI
340
+ //cleanupConnection();
341
+ break;
342
+ }
343
+ };
344
+
345
+ pc.ondatachannel = (event) => {
346
+ const dataChannelB = event.channel; // 这就是 A 端创建的通道
347
+
348
+ // 监听 B 端通道事件(接收 A 端消息用)
349
+ dataChannelB.onopen = () => {
350
+ console.log('B 端数据通道已打开');
351
+ };
352
+
353
+ dataChannelB.onmessage = (e) => {
354
+ console.log('B 收到消息:', e.data); // 输出 "Hello from A!"
355
+ };
356
+ };
357
+
358
+ // bind track
359
+ pc.ontrack = (event) => {
360
+ console.log('!!!!!!ontrack ok');
361
+ getVideo.srcObject = event.streams[0];
362
+ };
363
+
364
+ // 需要发送的媒体绑定到pc
365
+ // addTrack
366
+ stream.getTracks().forEach(
367
+ function (track) {
368
+ console.log('addTrack'),
369
+ pc.addTrack(
370
+ track,
371
+ stream
372
+ );
373
+ }
374
+ );
375
+ };
376
+
377
+ /*
378
+ 测试发送数据:
379
+ dataChannel.send("hello")
380
+ // 配置数据通道
381
+ */
382
+ function setupDataChannel(channel) {
383
+ channel.onopen = () => {
384
+ //updateStatus('数据通道已打开,可以发送消息和文件');
385
+ console.log('数据通道已打开,可以发送消息和文件');
386
+ //sendButton.disabled = false;
387
+ if (pc.iceConnectionState === 'connected') {
388
+ //sendFileButton.disabled = false;
389
+ }
390
+ };
391
+
392
+ channel.onmessage = (event) => {
393
+ handleIncomingData(event.data);
394
+ };
395
+
396
+ channel.onclose = () => {
397
+ console.log('数据通道已关闭');
398
+ //updateStatus('数据通道已关闭');
399
+ //sendButton.disabled = true;
400
+ //sendFileButton.disabled = true;
401
+ };
402
+
403
+ channel.onerror = (error) => {
404
+ console.error('数据通道错误:', error);
405
+ //updateStatus(`消息发送失败: ${error}`, true);
406
+ };
407
+ }
408
+
409
+ function addMessage(message) {
410
+ console.log("addMessage", message)
411
+ }
412
+
413
+ function updateProgress(percent) {
414
+ console.log("updateProgress", percent)
415
+ }
416
+
417
+ // 处理接收到的数据
418
+ function handleIncomingData(data) {
419
+ // 如果是文件元数据
420
+ if (typeof data === 'string' && data.startsWith('FILE_METADATA:')) {
421
+ const metadata = JSON.parse(data.replace('FILE_METADATA:', ''));
422
+ expectedFileSize = metadata.size;
423
+ receivedSize = 0;
424
+ receivedChunks = [];
425
+
426
+ addMessage(`正在接收文件: ${metadata.name} (${(metadata.size)})`);
427
+ //showProgress(metadata.name, 0);
428
+ return;
429
+ }
430
+
431
+ // 如果是文件结束标记
432
+ if (typeof data === 'string' && data === 'FILE_COMPLETE') {
433
+ const blob = new Blob(receivedChunks);
434
+ const fileUrl = URL.createObjectURL(blob);
435
+ addMessage(currentFileName, false, true, fileUrl);
436
+
437
+ // 清理
438
+ receivedChunks = [];
439
+ expectedFileSize = 0;
440
+ receivedSize = 0;
441
+ //hideProgress();
442
+ return;
443
+ }
444
+
445
+ // 如果是文件分片
446
+ if (data instanceof Blob) {
447
+ data.arrayBuffer().then(buffer => {
448
+ receivedChunks.push(buffer);
449
+ receivedSize += buffer.byteLength;
450
+
451
+ const percent = Math.round((receivedSize / expectedFileSize) * 100);
452
+ updateProgress(percent);
453
+ });
454
+ return;
455
+ }
456
+
457
+ // 否则是普通文本消息
458
+ addMessage(`对方: ${data}`, false);
459
+ }
460
+
461
+ /*
462
+ *
463
+ */
464
+ function sendFile() {
465
+ const fileInput = document.getElementById("fileInput");
466
+ //dataChannel && dataChannel.readyState === 'open'
467
+ if (fileInput.files.length == 0)
468
+ return;
469
+ currentFile = fileInput.files[0];
470
+ if (currentFile.size > 100 * 1024 * 1024) { // 限制100MB
471
+ alert('文件过大,请选择小于100MB的文件');
472
+ currentFile = null;
473
+ sendFileButton.disabled = true;
474
+ return;
475
+ }
476
+
477
+ // 确认channel状态
478
+ if (!currentFile || !dataChannel || dataChannel.readyState !== 'open') return;
479
+
480
+ // 发送文件元数据
481
+ const metadata = {
482
+ name: currentFile.name,
483
+ size: currentFile.size,
484
+ type: currentFile.type
485
+ };
486
+
487
+ dataChannel.send(`FILE_METADATA:${JSON.stringify(metadata)}`);
488
+ addMessage(`正在发送文件: ${currentFile.name} (${formatFileSize(currentFile.size)})`, true);
489
+
490
+ // 显示进度条
491
+ //showProgress(currentFile.name, 0);
492
+
493
+ // 分片读取并发送文件
494
+ let offset = 0;
495
+ fileReader = new FileReader();
496
+ // 读取文件
497
+ fileReader.onload = (e) => {
498
+ if (e.target.readyState === FileReader.DONE) {
499
+ // 发送分片
500
+ dataChannel.send(e.target.result);
501
+
502
+ // 更新进度
503
+ offset += e.target.result.byteLength;
504
+ const percent = Math.round((offset / currentFile.size) * 100);
505
+ updateProgress(percent);
506
+
507
+ // 继续读取下一分片
508
+ if (offset < currentFile.size) {
509
+ readNextChunk();
510
+ } else {
511
+ // 发送完成标记
512
+ dataChannel.send('FILE_COMPLETE');
513
+ addMessage(`文件发送完成: ${currentFile.name}`, true);
514
+ }
515
+ }
516
+ };
517
+
518
+ // 开始读取第一分片
519
+ readNextChunk();
520
+
521
+ function readNextChunk() {
522
+ const fileSlice = currentFile.slice(offset, offset + CHUNK_SIZE);
523
+ fileReader.readAsArrayBuffer(fileSlice);
524
+ }
525
+ }
526
+
527
+ function createOffer() {
528
+ //cds = []
529
+ var offerOptions = {
530
+ offerToReceiveAudio: 1,
531
+ offerToReceiveVideo: 1
532
+ };
533
+ //
534
+ dataChannel = pc.createDataChannel('chatAndFile', {
535
+ ordered: true, // 保证数据顺序
536
+ maxRetransmits: 3 // 最大重传次数
537
+ });
538
+ setupDataChannel(dataChannel);
539
+
540
+ // 监听远程数据通道
541
+ pc.ondatachannel = (event) => {
542
+ dataChannel = event.channel;
543
+ setupDataChannel(dataChannel);
544
+ };
545
+ pc.createOffer(
546
+ (desc) => {
547
+
548
+ //document.getElementById('offer').value = JSON.stringify(desc, null, 2 );
549
+ //socket.emit( 'chat offer', { to:getFromValue(), offer: desc } );
550
+ sendOffer(desc)
551
+ pc.setLocalDescription(desc, function () {
552
+ console.log("!!!!!!setLocalDescription ok");
553
+ }, (err) => {
554
+ console.error("setLocalDescription fail", err);
555
+ });
556
+ },
557
+ (error) => console.log('Failed to create session description: '
558
+ + error.toString()),
559
+ offerOptions);
560
+ }
561
+
562
+ function setOffer(desc) {
563
+ //const desc = document.getElementById('offer').value
564
+ if (!desc) {
565
+ alert('Please enter offer')
566
+ return false;
567
+ }
568
+ // 发起pc.onicecandidate前重置
569
+ cds = []
570
+ // 会立即触发:pc.onicecandidate
571
+ pc.setRemoteDescription((desc), () => {
572
+ // offer description
573
+ console.log('!!!!!!setRemoteDescription ok');
574
+ // 已经设置了remote description
575
+ createAnswer();
576
+ }, (error) => console.error('Failed to set session description: ' + error.toString()));
577
+ //console.log(desc)
578
+
579
+ return true;
580
+ }
581
+
582
+ // by socket
583
+ function sendOffer(desc) {
584
+ console.log('send offer', "to", getToValue(), desc)
585
+ socket.emit('chat offer', { from: getFromValue(),
586
+ to: getToValue(), offer: desc });
587
+ }
588
+
589
+ // pc
590
+ function createAnswer() {
591
+ pc.createAnswer((desc) => {
592
+ //document.getElementById('answer').value = JSON.stringify(desc, null, 2)
593
+ // answer with client description
594
+ pc.setLocalDescription(desc, function () {
595
+ console.log('!!!!!!setLocalDescription ok');
596
+ }, (error) => console.log('Failed to set session description: ' + error.toString())
597
+ );
598
+ // 特定类型的消息
599
+ //socket.emit('chat answer', desc);
600
+ sendAnswer(desc);
601
+ },
602
+ (error) => console.log('Failed to create session description: ' + error.toString()));
603
+ }
604
+
605
+ // by socket
606
+ function sendAnswer(desc) {
607
+ console.log('send answer', "to", getToValue(), desc)
608
+ socket.emit('chat answer', { from: getFromValue(),
609
+ to: getToValue(), answer: desc });
610
+ }
611
+
612
+ function setAnswer(desc) {
613
+ //const desc = document.getElementById('answer').value
614
+ if (!desc) {
615
+ //alert('Please enter answer')
616
+ return false;
617
+ }
618
+ //pc.remoteDescription
619
+ pc.setRemoteDescription((desc), function () {
620
+ console.log('!!!!!!setRemoteDescription ok');
621
+ }, (error) => console.log('Failed to set session description: ' + error.toString()));
622
+ }
623
+
624
+ function sendCandidate(candidate) {
625
+ console.log('send icecandidate', "to", getToValue(), candidate)
626
+ socket.emit('chat ice-candidate', { from: getFromValue(),
627
+ to: getToValue(), candidate: candidate });
628
+ }
629
+
630
+ function setCandidate(candidate) {
631
+ //const desc = document.getElementById('candidate').value
632
+ if ( typeof candidate!=="object" ) {
633
+ //alert('Please enter candidate')
634
+ return false;
635
+ }
636
+ //console.log( candidate )
637
+ //pc.remoteDescription
638
+ if ( candidate ){
639
+ pc.addIceCandidate((candidate), function () {
640
+ console.log('!!!!!!addIceCandidate ok');
641
+ }, (error) => console.log('Failed to add Ice Candidate: ' + error.toString()));
642
+ }
643
+ }
644
+
645
+ function initVideo() {
646
+ //srcVideo = document.getElementById('srcVideo');
647
+ // get stream
648
+ if (sendVideo.captureStream) {
649
+ // 从video里获取流?媒体流
650
+ stream = sendVideo.captureStream();
651
+ console.log('Captured stream from srcVideo with captureStream',
652
+ stream);
653
+ //call();
654
+ } else if (sendVideo.mozCaptureStream) {
655
+ stream = sendVideo.mozCaptureStream();
656
+ console.log('Captured stream from srcVideo with mozCaptureStream()',
657
+ stream);
658
+ //call();
659
+ } else {
660
+ console.error('captureStream() not supported');
661
+ }
662
+ var videoTracks = stream.getVideoTracks();
663
+ var audioTracks = stream.getAudioTracks();
664
+ if (videoTracks.length > 0) {
665
+ console.log('Using video device: ' + videoTracks[0].label);
666
+ }
667
+ if (audioTracks.length > 0) {
668
+ console.log('Using audio device: ' + audioTracks[0].label);
669
+ }
670
+ }
671
+
672
+ function sendMessage(str) {
673
+ const value = encryptWithKeyword(str, getEncKey())
674
+ const msg = {
675
+ command: "set",
676
+ from: from.value,
677
+ to: to.value,
678
+ content: value
679
+ }
680
+ socket.emit('chat message', msg);
681
+ }
682
+
683
+ function nextQuery(milli) {
684
+ // 如果是sendMessage立即收到的chat message;
685
+ if (milli) {
686
+ console.log("取消定时器", timer)
687
+ clearTimeout(timer)
688
+ }
689
+ const interval = milli || getInterval() || 15000
690
+ console.log("nextQuery", interval )
691
+ timer = setTimeout(loadprev, interval, socket)
692
+ }
693
+
694
+ form.addEventListener('submit', function (e) {
695
+ e.preventDefault();
696
+ if (prepost_chat() && input.value) {
697
+ sendMessage(input.value);
698
+ input.value = '';
699
+ } else {
700
+ alert("请输入内容|请选择接收方")
701
+ }
702
+ });
703
+
704
+ //checkScrollPosition()
705
+
706
+ // 监听滚动事件
707
+ messages.addEventListener('wheel', function (event) {
708
+ // 判断滚动方向
709
+ const isScrollingDown = event.deltaY > 0;
710
+ //scrollDirection.textContent = isScrollingDown ? '向下滚动' : '向上滚动';
711
+ //scrollDirection.className = `font-medium ${isScrollingDown ? 'text-green-600' : 'text-red-600'}`;
712
+
713
+ // 检查是否滚动到顶部或底部(使用setTimeout确保在滚动完成后检测)
714
+ setTimeout(checkScrollPosition, 10);
715
+ });
716
+
717
+ // 也监听scroll事件,确保在所有滚动方式下都能检测(如拖动滚动条)
718
+ messages.addEventListener('scroll', function () {
719
+ checkScrollPosition();
720
+ });
721
+
722
+ function buildItem(row) {
723
+ row.message = decryptWithKeyword(row.content, getEncKey())
724
+ return `<div class="item" id="${row.id}">
725
+ <div class="row">
726
+ <div class="time">${row.created}</div>
727
+ <div class="from">${row.from}</div>
728
+ </div>
729
+ <div class="message">${row.message}</div>
730
+ </div>`
731
+ }
732
+
733
+ function loadprev() {
734
+ if (from.value !== "unknow") {
735
+ const msg = {
736
+ command: "get",
737
+ queryConstraint: JSON.stringify({
738
+ startIdx: 0,
739
+ count: 10,
740
+ id: {
741
+ lt: firstId
742
+ },
743
+ from: from.value,
744
+ sql: `select * from mylife.chat where id > ${firstId} and (\`from\` = '${from.value}' or \`to\` = '${from.value}') limit 0, 10;`
745
+ })
746
+ }
747
+ socket.emit('chat message', msg);
748
+ }
749
+ }
750
+
751
+ // 检查滚动位置的函数
752
+ // "messages.scrollTop为什么总是0"
753
+ function checkScrollPosition() {
754
+
755
+ // 清除之前的状态类
756
+ messages.classList.remove('scrolled-top', 'scrolled-bottom');
757
+
758
+ // 滚动元素的属性
759
+ const scrollTop = messages.scrollTop; // 已滚动的顶部距离
760
+ const scrollHeight = messages.scrollHeight; // 内容总高度
761
+ const clientHeight = messages.clientHeight; // 可见区域高度
762
+ const isAtTop = scrollTop <= 1; // 允许1px误差
763
+ const isAtBottom = (scrollTop + clientHeight) >= (scrollHeight - 1); // 允许1px误差
764
+
765
+ // 更新状态显示
766
+
767
+ if (isAtTop && isAtBottom) {
768
+ console.log("内容不足以滚动的情况")
769
+ //scrollStatus.textContent = '内容未超出可视区域';
770
+ //scrollStatus.className = 'font-medium text-purple-600';
771
+ //hideEdgeIndicator();
772
+ } else if (isAtTop) {
773
+ console.log('已滚动到顶部');
774
+ //scrollStatus.className = 'font-medium text-blue-600';
775
+ //messages.classList.add('scrolled-top');
776
+ //showEdgeIndicator('已滚动到顶部', 'blue-100', 'blue-600');
777
+ // 避免触及载入
778
+ //loadprev();
779
+ } else if (isAtBottom) {
780
+ console.log('已滚动到底部');
781
+ //scrollStatus.className = 'font-medium text-green-600';
782
+ //messages.classList.add('scrolled-bottom');
783
+ //showEdgeIndicator('已滚动到底部', 'green-100', 'green-600');
784
+ } else {
785
+ console.log('正常位置');
786
+ //scrollStatus.className = 'font-medium text-gray-800';
787
+ //hideEdgeIndicator();
788
+ }
789
+ }
790
+
791
+ function closeChannel(){
792
+ if( socket )
793
+ socket.disconnect();
794
+ }
795
+
796
+ </script>