chattransfer 0.0.0-stage → 2.0.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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.ar.md +208 -0
  3. package/README.md +207 -2
  4. package/README.zh-CN.md +208 -0
  5. package/bin/chattransfer.js +38 -0
  6. package/frontend/README.md +5 -0
  7. package/frontend/dist/assets/browser-BqOeEQBb.js +8 -0
  8. package/frontend/dist/assets/index-DTsuxum5.js +976 -0
  9. package/frontend/dist/assets/index-Dw5X-Gok.css +1 -0
  10. package/frontend/dist/assets/logo-DQUxWwh1.png +0 -0
  11. package/frontend/dist/index.html +14 -0
  12. package/frontend/dist/pin.svg +1 -0
  13. package/frontend/dist/vite.svg +1 -0
  14. package/package.json +62 -4
  15. package/server/cli-options.js +69 -0
  16. package/server/config/loader.js +36 -0
  17. package/server/config.js +63 -0
  18. package/server/index.js +132 -0
  19. package/server/room-manager.js +75 -0
  20. package/server/routes/checkLogin.js +24 -0
  21. package/server/routes/files.js +49 -0
  22. package/server/routes/geneBUserQR.js +37 -0
  23. package/server/routes/geneQR.js +39 -0
  24. package/server/routes/getHomeDir.js +27 -0
  25. package/server/routes/getIndexInfo.js +42 -0
  26. package/server/routes/getIndexInfo2.js +42 -0
  27. package/server/routes/index.js +36 -0
  28. package/server/routes/openCachePath.js +63 -0
  29. package/server/routes/qr.js +63 -0
  30. package/server/routes/scanLogin.js +33 -0
  31. package/server/routes/session.js +15 -0
  32. package/server/routes/setCachePath.js +94 -0
  33. package/server/routes/system.js +107 -0
  34. package/server/services/message-history-service.js +476 -0
  35. package/server/services/message-service.js +31 -0
  36. package/server/socket/index.js +232 -0
  37. package/server/socket/session-manager.js +3 -0
  38. package/server/telemetry/amplitude-provider.js +17 -0
  39. package/server/telemetry/compat.js +17 -0
  40. package/server/telemetry/index.js +26 -0
  41. package/server/telemetry/noop-provider.js +7 -0
  42. package/server/tool.js +71 -0
  43. package/server/utils/app-root.js +11 -0
  44. package/server/utils/network.js +21 -0
  45. package/server/utils/request-url.js +15 -0
  46. package/server/utils/runtime-options.js +88 -0
  47. package/server/utils/runtime-paths.js +87 -0
@@ -0,0 +1,42 @@
1
+ const { geneQR } = require("../tool");
2
+
3
+
4
+ /**
5
+ * 获取首页信息
6
+ * @param {*} app
7
+ * @param {*} AEnterUrl
8
+ * @param {*} BEnterUrl
9
+ */
10
+ module.exports = (app, AEnterUrl,BEnterUrl) => {
11
+
12
+ app.get('/getIndexInfo', async (req, res) => {
13
+
14
+ try {
15
+
16
+ const qrABase64Str = geneQR(AEnterUrl);
17
+ const qrBBase64Str = geneQR(BEnterUrl);
18
+ const response = {
19
+ code: 0,
20
+ message: '获取成功',
21
+ data: {
22
+ "title": "",
23
+ "lists": [
24
+ {
25
+ "qrurl":qrABase64Str,
26
+ "jupmUrl":AEnterUrl
27
+ },
28
+ {
29
+ "qrurl": qrBBase64Str,
30
+ "jupmUrl":BEnterUrl
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ res.send(response);
36
+
37
+ } catch (err) {
38
+ console.log(err);
39
+ res.status(500).send(err);
40
+ }
41
+ });
42
+ }
@@ -0,0 +1,42 @@
1
+ const { geneQR } = require("../tool");
2
+
3
+
4
+ /**
5
+ * 获取首页信息
6
+ * @param {*} app
7
+ * @param {*} AEnterUrl
8
+ * @param {*} BEnterUrl
9
+ */
10
+ module.exports = (app,homeUrl, AEnterUrl,BEnterUrl) => {
11
+
12
+ app.get('/getIndexInfo2', async (req, res) => {
13
+
14
+ try {
15
+ // http://192.168.1.4:50001/login?receiver=A
16
+
17
+ const receiver = req.query.receiver;
18
+ // 指向 Vue 路由 /login,而不是旧的 html 文件
19
+ let loginUrl = `${homeUrl}/login?receiver=${receiver}`;
20
+ const qrABase64Str = geneQR(loginUrl);
21
+
22
+ const response = {
23
+ code: 0,
24
+ message: '获取成功',
25
+ data: {
26
+ "title": "",
27
+ "lists": [
28
+ {
29
+ "qrurl":qrABase64Str,
30
+ "jupmUrl":loginUrl
31
+ }
32
+ ]
33
+ }
34
+ }
35
+ res.send(response);
36
+
37
+ } catch (err) {
38
+ console.log(err);
39
+ res.status(500).send(err);
40
+ }
41
+ });
42
+ }
@@ -0,0 +1,36 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const { trackEvent } = require('../telemetry/compat');
4
+ const fs = require('fs');
5
+ const fileUpload = require('express-fileupload');
6
+ const { getCachePath } = require('../config');
7
+
8
+ module.exports = function registerRoutes(app) {
9
+ const uploadsDir = getCachePath();
10
+ const tempDir = path.join(uploadsDir, 'tmp');
11
+
12
+ if (!fs.existsSync(tempDir)) {
13
+ fs.mkdirSync(tempDir, { recursive: true });
14
+ }
15
+
16
+ app.use(fileUpload({
17
+ createParentPath: true,
18
+ useTempFiles: true,
19
+ tempFileDir: tempDir,
20
+ defParamCharset: 'utf8'
21
+ }));
22
+
23
+ app.use(express.json());
24
+
25
+ app.use(require('./system'));
26
+ app.use(require('./session'));
27
+ app.use(require('./qr'));
28
+ app.use(require('./files'));
29
+
30
+ app.use(express.static(path.join(__dirname, '../../frontend/dist')));
31
+ app.use(express.static(uploadsDir));
32
+
33
+ app.get('*', (req, res) => {
34
+ res.sendFile(path.join(__dirname, '../../frontend/dist/index.html'));
35
+ });
36
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 打开用户缓存目录
3
+ * @param {*} app
4
+ * @param {*} homeDir
5
+ */
6
+ module.exports = (app, uploadsDir) => {
7
+ app.post('/openCachePath', async (req, res) => {
8
+
9
+
10
+ try {
11
+ let response = {
12
+ code: 0 ,
13
+ message: '打开成功!',
14
+ data: {
15
+ 'platform':process.platform,
16
+ 'path':uploadsDir
17
+ }
18
+ }
19
+ var exec = require('child_process').exec;
20
+ if(process.platform === 'darwin'){
21
+ exec('open ' + uploadsDir, (error, stdout, stderr) => {
22
+
23
+ if (error) {
24
+ console.error(`exec error: ${error}`);
25
+ response.code = -1;
26
+ response.message = '打开失败!' + error.message;
27
+
28
+ }else {
29
+ console.log(`stdout: ${stdout}`);
30
+ console.error(`stderr: ${stderr}`);
31
+ response.code = 0;
32
+ response.message = '打开成功!';
33
+ }
34
+ console.log('response:', response);
35
+ res.send(response);
36
+ });
37
+ } else if (process.platform === 'win32') {
38
+ exec(`explorer "${uploadsDir}"`, (error, stdout, stderr) => {
39
+ if (error) {
40
+ console.error(`exec error: ${error}`);
41
+ response.code = -1;
42
+ response.message = '打开失败!' + error.message;
43
+ } else {
44
+ response.code = 0;
45
+ response.message = '打开成功!';
46
+ }
47
+ res.send(response);
48
+ });
49
+ } else {
50
+
51
+ response.code = -1;
52
+ response.message = '暂不支持的平台';
53
+ console.log('response:', response);
54
+ res.send(response);
55
+ }
56
+
57
+
58
+ } catch (err) {
59
+ console.log(err);
60
+ res.status(500).send(err);
61
+ }
62
+ });
63
+ }
@@ -0,0 +1,63 @@
1
+ const express = require('express');
2
+ const { geneQR } = require('../tool');
3
+ const { getRequestBaseUrl } = require('../utils/request-url');
4
+ const router = express.Router();
5
+
6
+ router.get('/geneQR', (req, res) => {
7
+ const content = req.query.qrStr;
8
+ const qrImage = geneQR(content);
9
+
10
+ res.send({
11
+ code: qrImage ? 0 : -1,
12
+ message: qrImage ? 'ok' : 'failed',
13
+ data: qrImage ? { qr: qrImage, qrText: content } : {}
14
+ });
15
+ });
16
+
17
+ router.get('/geneBUserQR', (req, res) => {
18
+ res.send({ code: 0, message: 'ok' });
19
+ });
20
+
21
+ router.get('/getIndexInfo', (req, res) => {
22
+ const homeUrl = getRequestBaseUrl(req);
23
+ const aUrl = `${homeUrl}/chat?sender=A&receiver=B`;
24
+ const bUrl = `${homeUrl}/chat?sender=B&receiver=A`;
25
+
26
+ res.send({
27
+ code: 0,
28
+ message: 'ok',
29
+ data: {
30
+ lists: [
31
+ { qrurl: geneQR(aUrl), jupmUrl: aUrl },
32
+ { qrurl: geneQR(bUrl), jupmUrl: bUrl }
33
+ ]
34
+ }
35
+ });
36
+ });
37
+
38
+ router.get('/getIndexInfo2', (req, res) => {
39
+ const homeUrl = getRequestBaseUrl(req);
40
+ const loginUrl = `${homeUrl}/login?receiver=${req.query.receiver || 'group_chat'}`;
41
+
42
+ res.send({
43
+ code: 0,
44
+ message: 'ok',
45
+ data: {
46
+ lists: [{ qrurl: geneQR(loginUrl), jupmUrl: loginUrl }]
47
+ }
48
+ });
49
+ });
50
+
51
+ router.get('/scanLogin', (req, res) => {
52
+ const homeUrl = getRequestBaseUrl(req);
53
+ const receiver = req.query.receiver || 'group_chat';
54
+ const targetUrl = `${homeUrl}/chat?sender=${req.query.sender}&receiver=${receiver}`;
55
+
56
+ res.send({
57
+ code: 0,
58
+ message: 'ok',
59
+ data: { targetUrl }
60
+ });
61
+ });
62
+
63
+ module.exports = router;
@@ -0,0 +1,33 @@
1
+ const { geneQR } = require("../tool");
2
+
3
+ /**
4
+ * 获取用户主目录
5
+ * @param {*} app
6
+ * @param {*} homeDir
7
+ */
8
+ module.exports = (app,chatUrl,AEnterUrl,BEnterUrl) => {
9
+ app.get('/scanLogin', async (req, res) => {
10
+
11
+
12
+ try {
13
+ const fromUser = req.query.sender;
14
+ const receiver = req.query.receiver;
15
+ if (!receiver) {
16
+ receiver = 'A';
17
+ }
18
+ let targetUrl = `${chatUrl}?sender=${fromUser}&receiver=${receiver}`
19
+ console.log("targetUrl:",targetUrl);
20
+ let response = {
21
+ code: 0 ,
22
+ message: '扫码成功!',
23
+ data: {
24
+ targetUrl:targetUrl
25
+ }
26
+ }
27
+ res.send(response);
28
+ } catch (err) {
29
+ console.log(err);
30
+ res.status(500).send(err);
31
+ }
32
+ });
33
+ }
@@ -0,0 +1,15 @@
1
+ const express = require('express');
2
+ const router = express.Router();
3
+ const roomManager = require('../socket/session-manager');
4
+
5
+ router.get('/checkLogin', (req, res) => {
6
+ res.send({
7
+ code: 0,
8
+ message: 'ok',
9
+ data: {
10
+ users: roomManager.listUsers()
11
+ }
12
+ });
13
+ });
14
+
15
+ module.exports = router;
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 设置自定义缓存目录
3
+ * 请求体: { path: string, migrate?: boolean }
4
+ * 生效方式: 修改后需重启进程(express.static / fileUpload 中间件运行时不可热切换)
5
+ * @param {*} app
6
+ * @param {*} uploadsDir 当前生效缓存目录
7
+ */
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const express = require('express');
11
+ const { setCachePath } = require('../config.js');
12
+ const { getAppWritableRoot } = require('../tool.js');
13
+
14
+ module.exports = (app, uploadsDir) => {
15
+ app.post('/setCachePath', express.json(), async (req, res) => {
16
+ try {
17
+ const inputPath = req.body && req.body.path;
18
+ const migrate = req.body ? req.body.migrate !== false : true;
19
+
20
+ // 1. 参数校验
21
+ if (!inputPath || typeof inputPath !== 'string' || !inputPath.trim()) {
22
+ return res.send({ code: -1, message: '请输入缓存目录路径' });
23
+ }
24
+
25
+ // 2. 必须绝对路径(先校验原始输入,再 resolve 规范化)
26
+ const trimmed = inputPath.trim();
27
+ if (!path.isAbsolute(trimmed)) {
28
+ return res.send({ code: -1, message: '请输入绝对路径' });
29
+ }
30
+ const newPath = path.resolve(trimmed);
31
+
32
+ // 3. 不能等于当前路径
33
+ if (newPath === path.resolve(uploadsDir)) {
34
+ return res.send({ code: -1, message: '新路径与当前路径相同' });
35
+ }
36
+
37
+ // 4. 不能等于应用根目录(避免污染应用文件)
38
+ const appRoot = getAppWritableRoot();
39
+ if (newPath === path.resolve(appRoot)) {
40
+ return res.send({ code: -1, message: '不能使用应用根目录作为缓存目录' });
41
+ }
42
+
43
+ // 5. 目录不存在则创建
44
+ try {
45
+ if (!fs.existsSync(newPath)) {
46
+ fs.mkdirSync(newPath, { recursive: true });
47
+ }
48
+ } catch (e) {
49
+ return res.send({ code: -1, message: '目录创建失败:' + e.message });
50
+ }
51
+
52
+ // 6. 可写性测试:写临时文件再删
53
+ const testFile = path.join(newPath, '.chattransfer-write-test');
54
+ try {
55
+ fs.writeFileSync(testFile, 'ok', 'utf8');
56
+ fs.unlinkSync(testFile);
57
+ } catch (e) {
58
+ return res.send({ code: -1, message: '路径无效或不可写' });
59
+ }
60
+
61
+ // 7. 迁移已有文件(递归复制旧目录内容到新目录)
62
+ if (migrate && fs.existsSync(uploadsDir)) {
63
+ try {
64
+ fs.cpSync(uploadsDir, newPath, { recursive: true });
65
+ // 清理测试遗留(若有)
66
+ if (fs.existsSync(testFile)) fs.unlinkSync(testFile);
67
+ } catch (e) {
68
+ // 迁移失败:清理已复制的内容,回滚,不更新配置
69
+ console.error('[setCachePath] migrate failed:', e.message);
70
+ return res.send({ code: -1, message: '迁移文件失败:' + e.message });
71
+ }
72
+ }
73
+
74
+ // 8. 持久化到 config.json
75
+ try {
76
+ setCachePath(newPath);
77
+ } catch (e) {
78
+ return res.send({ code: -1, message: '配置保存失败:' + e.message });
79
+ }
80
+
81
+ res.send({
82
+ code: 0,
83
+ message: '缓存目录设置成功,重启应用后生效',
84
+ data: {
85
+ newPath: newPath,
86
+ needsRestart: true
87
+ }
88
+ });
89
+ } catch (err) {
90
+ console.log(err);
91
+ res.status(500).send({ code: -1, message: err.message || '服务器内部错误' });
92
+ }
93
+ });
94
+ }
@@ -0,0 +1,107 @@
1
+ const express = require('express');
2
+ const path = require('path');
3
+ const { execFile } = require('child_process');
4
+ const router = express.Router();
5
+ const {
6
+ getCachePath,
7
+ getConfigFile,
8
+ getDefaultCachePath,
9
+ isCustomized,
10
+ setCachePath
11
+ } = require('../config');
12
+ const { getDefaultDataDir } = require('../utils/runtime-paths');
13
+ const { trackEvent } = require('../telemetry/compat');
14
+
15
+ router.get('/getHomeDir', (req, res) => {
16
+ const uploadsDir = getCachePath();
17
+
18
+ res.send({
19
+ code: 0,
20
+ message: 'ok',
21
+ data: {
22
+ uploadsDir,
23
+ defaultDir: getDefaultCachePath(),
24
+ isCustomized: isCustomized(),
25
+ configFile: getConfigFile()
26
+ }
27
+ });
28
+ });
29
+
30
+ router.get('/getVersionInfo', (req, res) => {
31
+ res.send({
32
+ code: 0,
33
+ message: 'ok',
34
+ data: {
35
+ version: require('../../package.json').version,
36
+ deviceId: 'anonymous'
37
+ }
38
+ });
39
+ });
40
+
41
+ router.post('/openCachePath', express.json(), (req, res) => {
42
+ const uploadsDir = getCachePath();
43
+ const respond = (code, message) => res.send({
44
+ code,
45
+ message,
46
+ data: { platform: process.platform, path: uploadsDir }
47
+ });
48
+
49
+ if (process.platform === 'linux' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
50
+ respond(0, '服务器无桌面环境,缓存目录已返回');
51
+ return;
52
+ }
53
+
54
+ if (process.platform === 'darwin') {
55
+ execFile('open', [uploadsDir], (error) => {
56
+ respond(error ? -1 : 0, error ? `打开失败:${error.message}` : '打开成功');
57
+ });
58
+ } else if (process.platform === 'win32') {
59
+ execFile('explorer', [uploadsDir], (error) => {
60
+ respond(error ? -1 : 0, error ? `打开失败:${error.message}` : '打开成功');
61
+ });
62
+ } else {
63
+ execFile('xdg-open', [uploadsDir], (error) => {
64
+ respond(error ? -1 : 0, error ? `打开失败:${error.message}` : '打开成功');
65
+ });
66
+ }
67
+ });
68
+
69
+ router.post('/setCachePath', (req, res) => {
70
+ const inputPath = req.body && req.body.path;
71
+
72
+ if (!inputPath || typeof inputPath !== 'string') {
73
+ return res.send({ code: -1, message: 'invalid path' });
74
+ }
75
+
76
+ const newPath = path.resolve(inputPath.trim());
77
+ const dataRoot = path.resolve(getDefaultDataDir());
78
+
79
+ if (!path.isAbsolute(newPath) || newPath === path.resolve(getCachePath()) || newPath === dataRoot) {
80
+ return res.send({ code: -1, message: 'invalid cache path' });
81
+ }
82
+
83
+ try {
84
+ require('fs').mkdirSync(newPath, { recursive: true });
85
+ setCachePath(newPath);
86
+ res.send({
87
+ code: 0,
88
+ message: 'ok',
89
+ data: { newPath, needsRestart: true }
90
+ });
91
+ } catch (error) {
92
+ res.send({ code: -1, message: error.message });
93
+ }
94
+ });
95
+
96
+ router.post('/api/track', (req, res) => {
97
+ const { eventType, eventProperties, userId } = req.body || {};
98
+
99
+ if (!eventType) {
100
+ return res.status(400).send({ code: -1, message: 'eventType is required' });
101
+ }
102
+
103
+ trackEvent(eventType, eventProperties || {}, userId || null);
104
+ res.send({ code: 0, message: 'ok' });
105
+ });
106
+
107
+ module.exports = router;