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.
- package/LICENSE +21 -0
- package/README.ar.md +208 -0
- package/README.md +207 -2
- package/README.zh-CN.md +208 -0
- package/bin/chattransfer.js +38 -0
- package/frontend/README.md +5 -0
- package/frontend/dist/assets/browser-BqOeEQBb.js +8 -0
- package/frontend/dist/assets/index-DTsuxum5.js +976 -0
- package/frontend/dist/assets/index-Dw5X-Gok.css +1 -0
- package/frontend/dist/assets/logo-DQUxWwh1.png +0 -0
- package/frontend/dist/index.html +14 -0
- package/frontend/dist/pin.svg +1 -0
- package/frontend/dist/vite.svg +1 -0
- package/package.json +62 -4
- package/server/cli-options.js +69 -0
- package/server/config/loader.js +36 -0
- package/server/config.js +63 -0
- package/server/index.js +132 -0
- package/server/room-manager.js +75 -0
- package/server/routes/checkLogin.js +24 -0
- package/server/routes/files.js +49 -0
- package/server/routes/geneBUserQR.js +37 -0
- package/server/routes/geneQR.js +39 -0
- package/server/routes/getHomeDir.js +27 -0
- package/server/routes/getIndexInfo.js +42 -0
- package/server/routes/getIndexInfo2.js +42 -0
- package/server/routes/index.js +36 -0
- package/server/routes/openCachePath.js +63 -0
- package/server/routes/qr.js +63 -0
- package/server/routes/scanLogin.js +33 -0
- package/server/routes/session.js +15 -0
- package/server/routes/setCachePath.js +94 -0
- package/server/routes/system.js +107 -0
- package/server/services/message-history-service.js +476 -0
- package/server/services/message-service.js +31 -0
- package/server/socket/index.js +232 -0
- package/server/socket/session-manager.js +3 -0
- package/server/telemetry/amplitude-provider.js +17 -0
- package/server/telemetry/compat.js +17 -0
- package/server/telemetry/index.js +26 -0
- package/server/telemetry/noop-provider.js +7 -0
- package/server/tool.js +71 -0
- package/server/utils/app-root.js +11 -0
- package/server/utils/network.js +21 -0
- package/server/utils/request-url.js +15 -0
- package/server/utils/runtime-options.js +88 -0
- package/server/utils/runtime-paths.js +87 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
const socketIo = require('socket.io');
|
|
2
|
+
const roomManager = require('./session-manager');
|
|
3
|
+
const { trackEvent } = require('../telemetry/compat');
|
|
4
|
+
const {
|
|
5
|
+
decorateGroupMessage,
|
|
6
|
+
decoratePrivateMessage
|
|
7
|
+
} = require('../services/message-service');
|
|
8
|
+
|
|
9
|
+
module.exports = function registerSocket(server, historyService) {
|
|
10
|
+
if (!historyService) {
|
|
11
|
+
throw new Error('historyService is required');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const io = socketIo(server, {
|
|
15
|
+
cors: {
|
|
16
|
+
origin: true,
|
|
17
|
+
methods: ['GET', 'POST']
|
|
18
|
+
},
|
|
19
|
+
pingTimeout: 30000,
|
|
20
|
+
pingInterval: 25000
|
|
21
|
+
});
|
|
22
|
+
const chatRoom = 'group-chat';
|
|
23
|
+
|
|
24
|
+
const getClientIp = (socket) => {
|
|
25
|
+
const address = socket.handshake.address || socket.request.connection.remoteAddress;
|
|
26
|
+
|
|
27
|
+
return address ? address.replace(/^::ffff:/, '') : '';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const broadcastUserList = () => {
|
|
31
|
+
io.to(chatRoom).emit('user_list_updated', {
|
|
32
|
+
users: roomManager.listUsers()
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const trackMessage = (type, direction, params, socket) => {
|
|
37
|
+
trackEvent('message_action', {
|
|
38
|
+
chat_type: type,
|
|
39
|
+
msg_type: params.msgType || 'TEXT',
|
|
40
|
+
direction,
|
|
41
|
+
sender: params.sender,
|
|
42
|
+
visitor_id: params.visitorId || 'unknown-visitor',
|
|
43
|
+
user_agent: socket.handshake.headers['user-agent']
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const persistMessage = async (message) => {
|
|
48
|
+
try {
|
|
49
|
+
return { message: await historyService.saveMessage(message), error: null };
|
|
50
|
+
} catch (error) {
|
|
51
|
+
console.error('[History] save message failed:', error.message);
|
|
52
|
+
return { message, error };
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const broadcastHistoryCleared = ({ chatType, targetUser, sessionId }, sourceSocket) => {
|
|
57
|
+
const payload = { chatType, targetUser, sessionId };
|
|
58
|
+
|
|
59
|
+
if (chatType === 'group') {
|
|
60
|
+
io.to(chatRoom).emit('history_cleared', payload);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
sourceSocket.emit('history_cleared', payload);
|
|
65
|
+
const receiver = roomManager.getUser(targetUser);
|
|
66
|
+
if (receiver && receiver.status === roomManager.getOnline()) {
|
|
67
|
+
io.to(receiver.socketId).emit('history_cleared', payload);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
io.on('connection', (socket) => {
|
|
72
|
+
console.log(`[Socket] connected: ${socket.id}`);
|
|
73
|
+
|
|
74
|
+
socket.on('login', ({ uid } = {}) => {
|
|
75
|
+
console.log(`[Socket] login: ${uid}`);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
socket.on('online', (username) => {
|
|
79
|
+
console.log(`[Socket] online: ${username} ${socket.id}`);
|
|
80
|
+
socket.username = username;
|
|
81
|
+
socket.join(chatRoom);
|
|
82
|
+
roomManager.setOnline({
|
|
83
|
+
username,
|
|
84
|
+
socketId: socket.id,
|
|
85
|
+
ip: getClientIp(socket)
|
|
86
|
+
});
|
|
87
|
+
broadcastUserList();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
socket.on('private_chat', async (params, ack) => {
|
|
91
|
+
const decorated = decoratePrivateMessage(params, roomManager.getUser(params.sender));
|
|
92
|
+
const { message: savedMessage, error } = await persistMessage(decorated);
|
|
93
|
+
|
|
94
|
+
if (typeof ack === 'function') {
|
|
95
|
+
ack(error ? { ...decorated, status: 'failed', error: 'Message persistence failed' } : savedMessage);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (error) return;
|
|
99
|
+
|
|
100
|
+
trackMessage('private', 'send', decorated, socket);
|
|
101
|
+
|
|
102
|
+
const receiver = roomManager.getUser(savedMessage.receiver);
|
|
103
|
+
|
|
104
|
+
if (receiver && receiver.status === roomManager.getOnline()) {
|
|
105
|
+
socket.to(receiver.socketId).emit('reply_private_chat', savedMessage);
|
|
106
|
+
|
|
107
|
+
trackEvent('message_action', {
|
|
108
|
+
chat_type: 'private',
|
|
109
|
+
msg_type: decorated.msgType || 'TEXT',
|
|
110
|
+
direction: 'receive',
|
|
111
|
+
receiver: decorated.receiver,
|
|
112
|
+
user_agent: socket.handshake.headers['user-agent']
|
|
113
|
+
});
|
|
114
|
+
} else {
|
|
115
|
+
console.log(`${decorated.receiver} is offline`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
socket.on('group_online', (username, nickname) => {
|
|
120
|
+
console.log(`[Socket] group_online: ${username} ${socket.id} nickname=${nickname}`);
|
|
121
|
+
socket.join(chatRoom);
|
|
122
|
+
socket.username = username;
|
|
123
|
+
roomManager.setOnline({
|
|
124
|
+
username,
|
|
125
|
+
socketId: socket.id,
|
|
126
|
+
nickname,
|
|
127
|
+
ip: getClientIp(socket)
|
|
128
|
+
});
|
|
129
|
+
socket.emit('group_name_updated', {
|
|
130
|
+
groupName: roomManager.getGroupName(),
|
|
131
|
+
updatedBy: ''
|
|
132
|
+
});
|
|
133
|
+
broadcastUserList();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
socket.on('update_nickname', ({ sender, nickname }) => {
|
|
137
|
+
roomManager.setNickname(sender, nickname);
|
|
138
|
+
broadcastUserList();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
socket.on('update_group_name', ({ groupName, updatedBy }) => {
|
|
142
|
+
roomManager.setGroupName(groupName);
|
|
143
|
+
io.to(chatRoom).emit('group_name_updated', {
|
|
144
|
+
groupName: roomManager.getGroupName(),
|
|
145
|
+
updatedBy: updatedBy || ''
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
socket.on('group_chat', async (params, ack) => {
|
|
150
|
+
const decorated = decorateGroupMessage(params, roomManager.getUser(params.sender));
|
|
151
|
+
const { message: savedMessage, error } = await persistMessage(decorated);
|
|
152
|
+
|
|
153
|
+
if (typeof ack === 'function') {
|
|
154
|
+
ack(error ? { ...decorated, status: 'failed', error: 'Message persistence failed' } : savedMessage);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (error) return;
|
|
158
|
+
|
|
159
|
+
trackMessage('group', 'send', decorated, socket);
|
|
160
|
+
socket.broadcast.to(chatRoom).emit('reply_group_chat', savedMessage);
|
|
161
|
+
|
|
162
|
+
trackEvent('message_action', {
|
|
163
|
+
chat_type: 'group',
|
|
164
|
+
msg_type: decorated.msgType || 'TEXT',
|
|
165
|
+
direction: 'broadcast',
|
|
166
|
+
user_agent: socket.handshake.headers['user-agent']
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
socket.on('history_query', async (params = {}, ack) => {
|
|
171
|
+
if (typeof ack !== 'function') return;
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const requester = socket.username || params.requester;
|
|
175
|
+
const chatType = params.chatType || (
|
|
176
|
+
params.targetUser === 'group_chat' ? 'group' : 'private'
|
|
177
|
+
);
|
|
178
|
+
const data = await historyService.queryHistory({
|
|
179
|
+
requester,
|
|
180
|
+
chatType,
|
|
181
|
+
targetUser: params.targetUser,
|
|
182
|
+
cursor: params.cursor,
|
|
183
|
+
limit: params.limit
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
ack({ code: 0, data });
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('[History] query failed:', error.message);
|
|
189
|
+
ack({ code: -1, message: 'Failed to load history' });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
socket.on('history_clear', async (params = {}, ack) => {
|
|
194
|
+
if (typeof ack !== 'function') return;
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const requester = socket.username || params.requester;
|
|
198
|
+
const chatType = params.chatType || (
|
|
199
|
+
params.targetUser === 'group_chat' ? 'group' : 'private'
|
|
200
|
+
);
|
|
201
|
+
const conversation = historyService.getConversation({
|
|
202
|
+
requester,
|
|
203
|
+
chatType,
|
|
204
|
+
targetUser: params.targetUser
|
|
205
|
+
});
|
|
206
|
+
const data = await historyService.clearHistory({
|
|
207
|
+
requester,
|
|
208
|
+
chatType,
|
|
209
|
+
targetUser: params.targetUser
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
broadcastHistoryCleared({
|
|
213
|
+
chatType,
|
|
214
|
+
targetUser: params.targetUser,
|
|
215
|
+
sessionId: conversation.sessionId
|
|
216
|
+
}, socket);
|
|
217
|
+
ack({ code: 0, data });
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('[History] clear failed:', error.message);
|
|
220
|
+
ack({ code: -1, message: 'Failed to clear history' });
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
socket.on('disconnect', () => {
|
|
225
|
+
console.log(`[Socket] disconnect: ${socket.username || 'unknown'} ${socket.id}`);
|
|
226
|
+
roomManager.setOffline(socket.username);
|
|
227
|
+
broadcastUserList();
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
return io;
|
|
232
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
|
|
3
|
+
class AmplitudeProvider {
|
|
4
|
+
constructor({ apiKey, endpoint }) {
|
|
5
|
+
this.apiKey = apiKey;
|
|
6
|
+
this.endpoint = endpoint || 'https://api2.amplitude.com/2/httpapi';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
async track(event) {
|
|
10
|
+
await axios.post(this.endpoint, {
|
|
11
|
+
api_key: this.apiKey,
|
|
12
|
+
events: [event]
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = AmplitudeProvider;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const { loadConfig } = require('../config/loader');
|
|
2
|
+
const { createTelemetryProvider } = require('./index');
|
|
3
|
+
|
|
4
|
+
const provider = createTelemetryProvider(loadConfig());
|
|
5
|
+
|
|
6
|
+
async function trackEvent(eventType, eventProperties = {}, customUserId = null) {
|
|
7
|
+
return provider.track({
|
|
8
|
+
event_type: eventType,
|
|
9
|
+
event_properties: eventProperties,
|
|
10
|
+
user_id: customUserId || undefined,
|
|
11
|
+
time: Date.now()
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = {
|
|
16
|
+
trackEvent
|
|
17
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { loadConfig } = require('../config/loader');
|
|
2
|
+
const AmplitudeProvider = require('./amplitude-provider');
|
|
3
|
+
const NoopProvider = require('./noop-provider');
|
|
4
|
+
|
|
5
|
+
const providers = {
|
|
6
|
+
amplitude: AmplitudeProvider
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function createTelemetryProvider(config = loadConfig()) {
|
|
10
|
+
const analytics = config.analytics || {};
|
|
11
|
+
const enabled = analytics.enabled && Boolean(analytics.apiKey);
|
|
12
|
+
const Provider = providers[analytics.provider || 'amplitude'];
|
|
13
|
+
|
|
14
|
+
if (!enabled || !Provider) {
|
|
15
|
+
return new NoopProvider();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return new Provider({
|
|
19
|
+
apiKey: analytics.apiKey,
|
|
20
|
+
endpoint: analytics.endpoint
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
createTelemetryProvider
|
|
26
|
+
};
|
package/server/tool.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execFile } = require('child_process');
|
|
4
|
+
var qr = require('qr-image');
|
|
5
|
+
module.exports.getIPAddress = function () {
|
|
6
|
+
var interfaces = os.networkInterfaces();
|
|
7
|
+
for (var devName in interfaces) {
|
|
8
|
+
var iface = interfaces[devName];
|
|
9
|
+
for (var i = 0; i < iface.length; i++) {
|
|
10
|
+
var alias = iface[i];
|
|
11
|
+
if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) {
|
|
12
|
+
return alias.address;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
//打开默认浏览器
|
|
19
|
+
module.exports.openDefaultBrowser = function (url, callback = () => {}) {
|
|
20
|
+
console.log("使用浏览器打开首页");
|
|
21
|
+
switch (process.platform) {
|
|
22
|
+
case "darwin":
|
|
23
|
+
execFile('open', [url], callback);
|
|
24
|
+
break;
|
|
25
|
+
case "win32":
|
|
26
|
+
execFile('cmd.exe', ['/c', 'start', '', url], callback);
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
execFile('xdg-open', [url], callback);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
module.exports.getAppWritableRoot = function () {
|
|
34
|
+
if (process.pkg) {
|
|
35
|
+
// Pkg环境:使用可执行文件所在目录(可写)
|
|
36
|
+
console.log('Running in pkg environment');
|
|
37
|
+
return path.dirname(process.execPath);
|
|
38
|
+
} else {
|
|
39
|
+
// 开发环境:使用项目根目录(server目录的上级)
|
|
40
|
+
console.log('Running in development environment');
|
|
41
|
+
return path.join(__dirname, '..');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
module.exports.initCachePath = function (cachePath,callback) {
|
|
45
|
+
console.log('开始初始化缓存目录');
|
|
46
|
+
|
|
47
|
+
// 如果不存在则创建,都则不创建
|
|
48
|
+
let fs = require('fs');
|
|
49
|
+
if(fs.existsSync(cachePath)){
|
|
50
|
+
console.log('缓存目录已存在,无需创建');
|
|
51
|
+
callback();
|
|
52
|
+
return;
|
|
53
|
+
};
|
|
54
|
+
fs.mkdir(cachePath, { recursive: true }, (err) => {
|
|
55
|
+
if (err) {
|
|
56
|
+
console.log('出错:',err)
|
|
57
|
+
} else {
|
|
58
|
+
console.log("初始化缓存目录成功")
|
|
59
|
+
}
|
|
60
|
+
callback(err);
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports.homeDir = os.userInfo().homedir;
|
|
66
|
+
module.exports.geneQR = function(url){
|
|
67
|
+
var img_string = qr.imageSync(url, { type: 'png' ,size:10});
|
|
68
|
+
const base64Str = 'data:image/png;base64,' + Buffer.from(img_string, 'utf8').toString('base64')
|
|
69
|
+
return base64Str;
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
|
|
3
|
+
function getIPAddress() {
|
|
4
|
+
const interfaces = os.networkInterfaces();
|
|
5
|
+
|
|
6
|
+
for (const interfaceName of Object.keys(interfaces)) {
|
|
7
|
+
const addresses = interfaces[interfaceName] || [];
|
|
8
|
+
|
|
9
|
+
for (const address of addresses) {
|
|
10
|
+
if (address.family === 'IPv4' && address.address !== '127.0.0.1' && !address.internal) {
|
|
11
|
+
return address.address;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
getIPAddress
|
|
21
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
function getRequestBaseUrl(req) {
|
|
2
|
+
const publicUrl = process.env.CHATTRANSFER_PUBLIC_URL;
|
|
3
|
+
|
|
4
|
+
if (publicUrl) {
|
|
5
|
+
return publicUrl.replace(/\/$/, '');
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const forwardedProtocol = req.headers['x-forwarded-proto'];
|
|
9
|
+
const protocol = forwardedProtocol || req.protocol;
|
|
10
|
+
return `${protocol}://${req.headers.host}`;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
getRequestBaseUrl
|
|
15
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
const DEFAULT_HOST = '0.0.0.0';
|
|
4
|
+
const DEFAULT_PORT = 55555;
|
|
5
|
+
|
|
6
|
+
function isTruthy(value) {
|
|
7
|
+
return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parsePort(value, source) {
|
|
11
|
+
const port = Number(value);
|
|
12
|
+
|
|
13
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
14
|
+
throw new Error(`Invalid ${source}: ${value}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return port;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizePublicUrl(value) {
|
|
21
|
+
if (!value) return null;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
const url = new URL(value);
|
|
25
|
+
|
|
26
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
27
|
+
throw new Error('Invalid public URL protocol');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const basePath = url.pathname === '/' ? '' : url.pathname.replace(/\/$/, '');
|
|
31
|
+
return `${url.protocol}//${url.host}${basePath}`;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
throw new Error(`Invalid public URL: ${value}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function shouldOpenBrowser({ env = process.env, explicitValue, platform = process.platform } = {}) {
|
|
38
|
+
if (typeof explicitValue === 'boolean') {
|
|
39
|
+
return explicitValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (env.CHATTRANSFER_NO_BROWSER !== undefined) {
|
|
43
|
+
return !isTruthy(env.CHATTRANSFER_NO_BROWSER);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (platform === 'linux' && !env.DISPLAY && !env.WAYLAND_DISPLAY) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveStartOptions(overrides = {}, env = process.env) {
|
|
54
|
+
const portValue = overrides.port ?? env.CHATTRANSFER_PORT;
|
|
55
|
+
const port = portValue === undefined || portValue === null || portValue === ''
|
|
56
|
+
? DEFAULT_PORT
|
|
57
|
+
: parsePort(portValue, 'port');
|
|
58
|
+
const host = overrides.host ?? env.CHATTRANSFER_HOST ?? DEFAULT_HOST;
|
|
59
|
+
const dataDir = overrides.dataDir ?? env.CHATTRANSFER_DATA_DIR;
|
|
60
|
+
const publicUrl = normalizePublicUrl(overrides.publicUrl ?? env.CHATTRANSFER_PUBLIC_URL);
|
|
61
|
+
const openBrowser = shouldOpenBrowser({
|
|
62
|
+
env,
|
|
63
|
+
explicitValue: overrides.openBrowser
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (typeof host !== 'string' || !host.trim()) {
|
|
67
|
+
throw new Error('Invalid host');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
host: host.trim(),
|
|
72
|
+
port,
|
|
73
|
+
explicitPort: overrides.port !== undefined || env.CHATTRANSFER_PORT !== undefined,
|
|
74
|
+
dataDir: dataDir ? path.resolve(String(dataDir)) : null,
|
|
75
|
+
publicUrl,
|
|
76
|
+
openBrowser
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
DEFAULT_HOST,
|
|
82
|
+
DEFAULT_PORT,
|
|
83
|
+
isTruthy,
|
|
84
|
+
normalizePublicUrl,
|
|
85
|
+
parsePort,
|
|
86
|
+
resolveStartOptions,
|
|
87
|
+
shouldOpenBrowser
|
|
88
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
const os = require('os');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const RUNTIMES = new Set(['development', 'npm', 'pkg', 'docker']);
|
|
5
|
+
|
|
6
|
+
function getRuntimeName() {
|
|
7
|
+
const runtime = process.env.CHATTRANSFER_RUNTIME;
|
|
8
|
+
|
|
9
|
+
if (runtime && RUNTIMES.has(runtime)) {
|
|
10
|
+
return runtime;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return process.pkg ? 'pkg' : 'development';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function getUserDataDir() {
|
|
17
|
+
if (process.platform === 'darwin') {
|
|
18
|
+
return path.join(os.homedir(), 'Library', 'Application Support', 'ChatTransfer');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if (process.platform === 'win32') {
|
|
22
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
23
|
+
return path.join(appData, 'ChatTransfer');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
|
|
27
|
+
return path.join(xdgDataHome, 'ChatTransfer');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getDefaultDataDir() {
|
|
31
|
+
const explicitDataDir = process.env.CHATTRANSFER_DATA_DIR;
|
|
32
|
+
|
|
33
|
+
if (explicitDataDir) {
|
|
34
|
+
return path.resolve(explicitDataDir);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.env.CHATTRANSFER_CONFIG_FILE) {
|
|
38
|
+
return path.dirname(path.resolve(process.env.CHATTRANSFER_CONFIG_FILE));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const runtime = getRuntimeName();
|
|
42
|
+
|
|
43
|
+
if (runtime === 'pkg') {
|
|
44
|
+
return path.dirname(process.execPath);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (runtime === 'npm' || runtime === 'docker') {
|
|
48
|
+
return getUserDataDir();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return path.join(__dirname, '..', '..');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getConfigFile() {
|
|
55
|
+
return process.env.CHATTRANSFER_CONFIG_FILE
|
|
56
|
+
? path.resolve(process.env.CHATTRANSFER_CONFIG_FILE)
|
|
57
|
+
: path.join(getDefaultDataDir(), 'config.json');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function getTelemetryConfigFile() {
|
|
61
|
+
return path.join(getDefaultDataDir(), 'config', 'local.json');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getDatabaseFile() {
|
|
65
|
+
const runtime = getRuntimeName();
|
|
66
|
+
|
|
67
|
+
if (runtime === 'npm' || runtime === 'docker') {
|
|
68
|
+
return path.join(getDefaultDataDir(), 'chattransfer.db');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return path.join(getDefaultDataDir(), 'uploads', 'history', 'chattransfer.db');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function usesIndependentDatabaseFile() {
|
|
75
|
+
const runtime = getRuntimeName();
|
|
76
|
+
return runtime === 'npm' || runtime === 'docker';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
getConfigFile,
|
|
81
|
+
getDatabaseFile,
|
|
82
|
+
getDefaultDataDir,
|
|
83
|
+
getRuntimeName,
|
|
84
|
+
getTelemetryConfigFile,
|
|
85
|
+
getUserDataDir,
|
|
86
|
+
usesIndependentDatabaseFile
|
|
87
|
+
};
|