feat: 环境部署相关完善

feat: 调整turn服务帐号默认为有效帐号模式
feat: 文档更新
This commit is contained in:
https://blog.iamtsm.cn
2023-08-05 01:50:49 +08:00
parent f0ac8972ce
commit 8044e369fe
29 changed files with 580 additions and 148 deletions

0
CHANGELOG.md Normal file
View File

View File

@@ -1,7 +1,9 @@
FROM node:lts-alpine FROM node:lts-alpine
COPY svr /app/svr
WORKDIR /app/svr COPY svr /tlrtcfile/svr
WORKDIR /tlrtcfile/svr
RUN npm install --registry=https://registry.npmmirror.com && npm run build:pro RUN npm install --registry=https://registry.npmmirror.com && npm run build:pro
ENTRYPOINT ["node"] ENTRYPOINT ["node"]

100
README.md
View File

@@ -11,9 +11,9 @@
#### 简介 tl webrtc datachannel filetools用webrt在web端传输文件支持传输超大文件。 #### 简介 tl webrtc datachannel filetools用webrt在web端传输文件支持传输超大文件。
#### 优点 分片传输,跨终端,不限平台,方便使用,内网不限速,支持私有部署,支持多文件拖拽发送 #### 优点 分片传输,跨终端,不限平台,方便使用,内网不限速局域网最高到过70多M/s,支持私有部署,支持多文件拖拽发送,网页文件预览
#### 扩展 扩展了许多丰富的小功能,如本地屏幕录制,远程屏幕共享,远程音视频通话,密码房间,直播,oss云存储中继服务设置webrtc检测统计文字传输公共聊天远程画板丰富的后台管理实时执行日志展示机器人告警通知等功能... 等等 #### 扩展 扩展了许多丰富的小功能,如本地屏幕录制,远程屏幕共享(无延迟),远程音视频通话(无延迟),直播(无延迟)密码房间oss云存储中继服务设置webrtc检测webrtc统计,文字传输(群聊,私聊),公共聊天,远程画板,AI聊天框丰富的后台管理,实时执行日志展示,机器人告警通知等功能... 等等
#### 体验 https://im.iamtsm.cn/file #### 体验 https://im.iamtsm.cn/file
@@ -21,15 +21,17 @@
## 准备 (必须步骤) ## 准备 (必须步骤)
安装node-14.21.x或14.21.x以上npm后进入项目目录运行下面命令 安装node-14.21.x或14.21.x以上npm后进入项目目录运行下面命令
`cd svr/` `cd svr/`
`npm install` `npm install`
首次运行/自行开发页面,需要启动下面两个命令之一 首次运行/自行开发页面,下面两个命令之一即可
`npm run build:dev` (打包监听文件改动打包min) 或者 `npm run build:pro` 打包min `npm run build:dev` (如果你需要自己开发/修改前端页面,用这个命令)
`npm run build:pro` (不需要开发/修改前端页面,用这个命令)
## 配置websocket (必须步骤) ## 配置websocket (必须步骤)
@@ -37,18 +39,18 @@
"ws": { "ws": {
"port": 8444, #socket 端口 "port": 8444, #socket 端口
"host": "ws://域名 或者 ip:port 或者 域名:port", #socket ip 局域网ip/公网ip, 局域网ip只能在局域网访问公网ip可在公网访问 "host": "ws://域名 或者 ip:port 或者 域名:port", #socket ip 局域网ip/公网ip, 局域网ip只能在局域网访问公网ip可在公网访问
}, },
"wss" : { "wss" : {
"port": 8444, #socket 端口 "port": 8444, #socket 端口
"host": "wss://域名 或者 ip:port 或者 域名:port", #socket ip 局域网ip/公网ip, 局域网ip只能在局域网访问公网ip可在公网访问 "host": "wss://域名 或者 ip:port 或者 域名:port", #socket ip 局域网ip/公网ip, 局域网ip只能在局域网访问公网ip可在公网访问
}, },
常见情况示例 : 常见情况示例 :
比如你是用ip(10.x.x.x)的形式部署socket服务那么host就为 比如你是用ip(10.1.2.3)的形式部署socket服务那么host就为
ws://10.x.x.x:8444 或者 wss://10.x.x.x:8444 ws://10.1.2.3:8444 或者 wss://10.1.2.3:8444
如果你有域名并且配置了代理比如a.test.com转发到本地socket服务的8444端口那么host就为 如果你有域名并且配置了代理比如a.test.com转发到本地socket服务的8444端口那么host就为
@@ -56,24 +58,54 @@
如果你有域名但是没有转发到具体的端口比如有b.test.com:8444访问的是socket服务的8444端口那么host就为 如果你有域名但是没有转发到具体的端口比如有b.test.com:8444访问的是socket服务的8444端口那么host就为
ws://b.test.com:8444 或者 wss://b.test.com ws://b.test.com:8444 或者 wss://b.test.com:8444
## 启动 (必须步骤) ## 启动 (必须步骤)
启动以下两个服务, 选一种模式启动即可 启动以下两个服务, 选一种模式启动即可
http模式 http模式启动后,访问 http://你的机器ip:9092 即可
api服务: `npm run lapi` api服务: `npm run lapi`
socket服务 : `npm run lsocket` socket服务 : `npm run lsocket`
https模式 https模式启动后,访问 https://你的机器ip:9092 即可
api服务: `npm run sapi` api服务: `npm run sapi`
socket服务 : `npm run ssocket` socket服务 : `npm run ssocket`
## 配置turnserver (局域网非必须步骤,公网必须步骤)
目前有两种形式去生成使用turn服务的帐号密码一种是固定帐号密码 (优先推荐),一种是有效期帐号密码。**选一种方式即可**
ubuntu示例:
安装coturn `sudo apt-get install coturn`
有效帐号密码 : `docker/coturn/turnserver-with-secret-user.conf`
1. 修改 `listening-device`, `listening-ip`, `external-ip`, `static-auth-secret`, `realm` 几个字段即可
2. 启动turnserver
`turnserver -c /这个地方路径填完整/conf/turn/turnserver-with-secret-user.conf`
固定帐号密码 : `docker/coturn/turnserver-with-fixed-user.conf`
1. 修改 `listening-device`, `listening-ip`, `external-ip`, `user`, `realm` 几个字段即可
2. 生成用户
`turnadmin -a -u 帐号 -p 密码 -r 这个地方填配置文件中的relam`
3. 启动turnserver
`turnserver -c /这个地方路径填完整/docker/coturn/turnserver-with-secret-user.conf`
## 配置数据库 (非必须步骤) ## 配置数据库 (非必须步骤)
修改cfg.json中相应数据库配置 修改cfg.json中相应数据库配置
@@ -112,35 +144,35 @@ https模式
} }
} }
## 配置turnserver (局域网非必须步骤,公网必须步骤)
ubuntu示例:
安装coturn `sudo apt-get install coturn`
项目提供了一份配置文件模板在 : `conf/turn/turnserver.conf`
修改配置文件后复制一份 `cp conf/turn/turnserver.conf /etc/turnserver.conf`
示例用户和密码: tlrtcfile
生成用户 (turnadmin生成密码) `turnadmin -k -u tlrtcfile -r 你的域名`
或者 (自定义密码) `turnadmin -a -u tlrtcfile -p tlrtcfile -r 你的域名`
启动turnserver `turnserver -c /etc/turnserver.conf`
可参考示例模板 : `bin/turnStart.sh`
## Docker (非必须步骤) ## Docker (非必须步骤)
修改local.env中的配置信息或者按需配置conf.json中的ws, 或者wss (需要填容器的ip端口信息)
docker-compose up -d #### 使用官方镜像 :
docker pull iamtsm/tl-rtc-file
#### 自己打包镜像 :
两种模式选一种操作即可
http模式启动:
修改 `docker/local.env` 中的配置信息或者按需配置conf.json中的ws, 或者wss (需要填容器的ip端口信息)
docker-compose --profile=local up -d
访问 : http://localhost:9092 或者 http://本机ip:9092 访问 : http://localhost:9092 或者 http://本机ip:9092
https模式启动:
修改 `docker/local.env` 中的配置信息或者按需配置conf.json中的ws, 或者wss (需要填容器的ip端口信息)
docker-compose --profile=server up -d
访问 : https://localhost:9092 或者 https://本机ip:9092
## 管理后台 (非必须步骤) ## 管理后台 (非必须步骤)
前提 需要开启数据库配置 前提 : 需要开启数据库配置
修改cfg.json中的manage的room和password默认房间号和密码都是tlrtcfile 修改cfg.json中的manage的room和password默认房间号和密码都是tlrtcfile

View File

@@ -1,18 +1,46 @@
version: '3' version: '3'
services: services:
api: api-local:
profiles: ['local']
env_file: env_file:
- local.env - docker/local.env
build: . build: .
ports: ports:
- 9092:9092 - 9092:9092
links:
- socket
command: localapi command: localapi
socket:
socket-local:
profiles: ['local']
env_file: env_file:
- local.env - docker/local.env
build: . build: .
ports: ports:
- 8444:8444 - 8444:8444
command: localsocket command: localsocket
api-server:
profiles: ['server']
env_file:
- docker/server.env
build: .
ports:
- 9092:9092
command: serverapi
socket-server:
profiles: ['server']
env_file:
- docker/server.env
build: .
ports:
- 8444:8444
command: serversocket
# coturn:
# image: coturn/coturn
# restart: always
# ports:
# - "3478:3478/udp"
# - "3478:3478/tcp"
# volumes:
# - ./docker/coturn/turnserver-with-secret-user.conf:/etc/turnserver.conf:ro

1
docker/coturn/coturn.env Normal file
View File

@@ -0,0 +1 @@
#coturn env feature use

View File

@@ -1,12 +1,12 @@
#------------TURN BASE CONFIG------------# #------------TURN BASE CONFIG 基础配置------------#
#监听网卡 #监听网卡
listening-device=eth0 # listening-device=eth0
#监听ip(内网ip) #监听ip(内网ip)
listening-ip= # listening-ip=
#监听端口(端口) #监听端口(端口)
listening-port=3478 listening-port=3478
#公网ip #公网ip
external-ip= # external-ip=
#端口最小值 #端口最小值
min-port=49152 min-port=49152
#端口最大值 #端口最大值
@@ -26,21 +26,8 @@ no-tls
#关闭dtls #关闭dtls
no-dtls no-dtls
#------------TURN USER CONFIG------------# #------------TURN USER CONFIG 固定帐号密码模式------------#
#用户账号密码 #用户账号密码
user=tlrtcfile:tlrtcfile user=tlrtcfile:tlrtcfile
#来源(域名或ip:port) #来源(域名或ip:port)
realm= realm=tlrtcfile.com
#------------TURN REST API USER CONFIG------------#
#开启rest api
#use-auth-secret
#rest api账号密码
#static-auth-secret=this-is-the-secret-configured-for-coturn-server
#来源(域名或ip:port)
#realm=

View File

@@ -0,0 +1,35 @@
#------------TURN BASE CONFIG 基础配置------------#
#监听网卡
# listening-device=eth0
#监听ip(内网ip)
# listening-ip=
#监听端口(端口)
listening-port=3478
#公网ip
# external-ip=
#端口最小值
min-port=49152
#端口最大值
min-port=55000
#cli密码
cli-password=qwerty
#后台运行
daemon
#会话指纹
fingerprint
#中等详细日志
verbose
#长期凭证
# lt-cred-mech
#关闭tls
no-tls
#关闭dtls
no-dtls
#------------TURN REST API USER CONFIG 有效时间帐号密码模式 (优先推荐使用这种方式)------------#
#开启rest api
use-auth-secret
#rest api账号密码
static-auth-secret=tlrtcfile
#来源(域名或ip:port)
realm=tlrtcfile.com

View File

@@ -1,6 +1,6 @@
ENV_MODE=local ENV_MODE=local
WEBRTC_STUN_HOST=stun:stun.xten.com WEBRTC_STUN_HOST=stun:stun.xten.com
WEBRTC_TURN_HOST=turn:global.turn.twilio.com:3478?transport=udp WEBRTC_TURN_HOST=turn:global.turn.twilio.com:3478?transport=udp
WEBRTC_TURN_USERNAME=dc2d2894d5a9023620c467b0e71cfa6a35457e6679785ed6ae9856fe5bdfa269 WEBRTC_TURN_USERNAME=tlrtcfile
WEBRTC_TURN_CREDENTIAL=tE2DajzSJwnsSbc123 WEBRTC_TURN_CREDENTIAL=tlrtcfile
WS_HOST=ws://127.0.0.1:8444 WS_HOST=ws://127.0.0.1:8444

1
docker/mysql/mysql.env Normal file
View File

@@ -0,0 +1 @@
#mysql env feature use

6
docker/server.env Normal file
View File

@@ -0,0 +1,6 @@
ENV_MODE=server
WEBRTC_STUN_HOST=stun:stun.xten.com
WEBRTC_TURN_HOST=turn:global.turn.twilio.com:3478?transport=udp
WEBRTC_TURN_USERNAME=tlrtcfile
WEBRTC_TURN_CREDENTIAL=tlrtcfile
WSS_HOST=wss://127.0.0.1:8444

View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Step 1: Check if Node.js is installed and install Node.js 18 if not
if ! command -v node &> /dev/null; then
echo "Node.js is not installed. Installing Node.js 18..."
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
fi
# Step 2: Output Node.js and npm versions
node_version=$(node -v)
npm_version=$(npm -v)
echo "Node.js version: $node_version"
echo "npm version: $npm_version"
sleep 1
# Step 3: Check if pm2 is installed and install it globally if not
if ! command -v pm2 &> /dev/null; then
echo "pm2 is not installed. Installing pm2 globally..."
sudo npm install -g pm2
fi
# Step 4: Output pm2 version
pm2_version=$(pm2 -v)
echo "pm2 version: $pm2_version"
sleep 1
# Step 5: Check if ports 9092 and 8444 are occupied
port_9092_in_use=$(sudo lsof -i :9092 | grep LISTEN | wc -l)
port_8444_in_use=$(sudo lsof -i :8444 | grep LISTEN | wc -l)
if [ "$port_9092_in_use" -gt 0 ] || [ "$port_8444_in_use" -gt 0 ]; then
echo "Port 9092 or 8444 is already in use."
exit 1
fi
echo "ready to run auto-start-local.sh"
sleep 1
# Step 6: Run start-local.sh script to start the service
./auto-start-local.sh

View File

@@ -0,0 +1,41 @@
#!/bin/bash
# Step 1: Check if Node.js is installed and install Node.js 18 if not
if ! command -v node &> /dev/null; then
echo "Node.js is not installed. Installing Node.js 18..."
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
fi
# Step 2: Output Node.js and npm versions
node_version=$(node -v)
npm_version=$(npm -v)
echo "Node.js version: $node_version"
echo "npm version: $npm_version"
sleep 1
# Step 3: Check if pm2 is installed and install it globally if not
if ! command -v pm2 &> /dev/null; then
echo "pm2 is not installed. Installing pm2 globally..."
sudo npm install -g pm2
fi
# Step 4: Output pm2 version
pm2_version=$(pm2 -v)
echo "pm2 version: $pm2_version"
sleep 1
# Step 5: Check if ports 9092 and 8444 are occupied
port_9092_in_use=$(sudo lsof -i :9092 | grep LISTEN | wc -l)
port_8444_in_use=$(sudo lsof -i :8444 | grep LISTEN | wc -l)
if [ "$port_9092_in_use" -gt 0 ] || [ "$port_8444_in_use" -gt 0 ]; then
echo "Port 9092 or 8444 is already in use."
exit 1
fi
echo "ready to run auto-start-server.sh"
sleep 1
# Step 6: Run start-local.sh script to start the service
./auto-start-server.sh

View File

@@ -6,4 +6,4 @@ pm2 start npm --name=tl-rtc-file-socket-local -- run lsocket
sleep 1 sleep 1
pm2 start npm --name=tl-rtc-file-build-local -- run build:dev npm run build:pro

View File

@@ -0,0 +1,9 @@
pm2 start npm --name=tl-rtc-file-api-server -- run sapi
sleep 1
pm2 start npm --name=tl-rtc-file-socket-server -- run ssocket
sleep 1
npm run build:pro

3
svr/bin/linux/auto-stop.sh Executable file
View File

@@ -0,0 +1,3 @@
pm2 stop all
echo "stop all pm2 processes ok"

View File

@@ -1,8 +0,0 @@
#生成账号密码方式1
turnadmin -a -u tlrtcfile -p tlrtcfile -r 域名或ip:port
#生成账号密码方式2
turnadmin -k -u tlrtcfile -r 域名或ip:port
#启动turnserver
turnserver -c turnserver.conf路径

View File

@@ -0,0 +1,45 @@
@echo off
setlocal
REM Step 1: Check if Node.js is installed and install Node.js 18 if not
where /q node
if %ERRORLEVEL% NEQ 0 (
echo Node.js is not installed. Installing Node.js 18...
REM Modify the Node.js installer URL if needed
curl -o node_installer.msi https://nodejs.org/dist/v18.0.0/node-v18.0.0-x64.msi
start /wait msiexec /i node_installer.msi /qn
del node_installer.msi
)
REM Step 2: Output Node.js and npm versions
node -v
npm -v
REM Step 3: Check if pm2 is installed and install it globally if not
where /q pm2
if %ERRORLEVEL% NEQ 0 (
echo pm2 is not installed. Installing pm2 globally...
npm install -g pm2
)
REM Step 4: Output pm2 version
pm2 -v
REM Step 5: Check if ports 9092 and 8444 are occupied
netstat -ano | findstr ":9092"
if %ERRORLEVEL% EQU 0 (
echo Port 9092 is already in use.
exit /b 1
)
netstat -ano | findstr ":8444"
if %ERRORLEVEL% EQU 0 (
echo Port 8444 is already in use.
exit /b 1
)
REM Step 6: Output installation successful message
echo env Installation successful.
REM Step 7: Run start-local.bat script to start the service
auto-start-local.bat

View File

@@ -0,0 +1,45 @@
@echo off
setlocal
REM Step 1: Check if Node.js is installed and install Node.js 18 if not
where /q node
if %ERRORLEVEL% NEQ 0 (
echo Node.js is not installed. Installing Node.js 18...
REM Modify the Node.js installer URL if needed
curl -o node_installer.msi https://nodejs.org/dist/v18.0.0/node-v18.0.0-x64.msi
start /wait msiexec /i node_installer.msi /qn
del node_installer.msi
)
REM Step 2: Output Node.js and npm versions
node -v
npm -v
REM Step 3: Check if pm2 is installed and install it globally if not
where /q pm2
if %ERRORLEVEL% NEQ 0 (
echo pm2 is not installed. Installing pm2 globally...
npm install -g pm2
)
REM Step 4: Output pm2 version
pm2 -v
REM Step 5: Check if ports 9092 and 8444 are occupied
netstat -ano | findstr ":9092"
if %ERRORLEVEL% EQU 0 (
echo Port 9092 is already in use.
exit /b 1
)
netstat -ano | findstr ":8444"
if %ERRORLEVEL% EQU 0 (
echo Port 8444 is already in use.
exit /b 1
)
REM Step 6: Output installation successful message
echo env Installation successful.
REM Step 7: Run start-local.bat script to start the service
auto-start-local.bat

View File

@@ -0,0 +1,12 @@
@echo off
REM Start the first process
start "" pm2 start npm --name=tl-rtc-file-api-local -- run lapi
timeout /t 1 /nobreak
REM Start the second process
start "" pm2 start npm --name=tl-rtc-file-socket-local -- run lsocket
timeout /t 1 /nobreak
REM Run npm build command
npm run build:pro

View File

@@ -0,0 +1,12 @@
@echo off
REM Start the first process
start "" pm2 start npm --name=tl-rtc-file-api-local -- run sapi
timeout /t 1 /nobreak
REM Start the second process
start "" pm2 start npm --name=tl-rtc-file-socket-local -- run ssocket
timeout /t 1 /nobreak
REM Run npm build command
npm run build:pro

View File

@@ -1,5 +1,5 @@
{ {
"version": "10.2.8", "version": "10.2.9",
"ws": { "ws": {
"port": 8444, "port": 8444,
"host": "ws://127.0.0.1:8444" "host": "ws://127.0.0.1:8444"

View File

@@ -1,23 +1,24 @@
const inject_env_config=(conf)=>{ const inject_env_config = (conf) => {
Object.keys(process.env).filter(key=>/^(WS(S)?_|API_|WEBRTC_).+/.test(key)).map(key=>{ Object.keys(process.env).filter(key => /^(WS(S)?_|API_|WEBRTC_).+/.test(key)).map(key => {
let data=process.env[key] let data = process.env[key]
if(key.endsWith('_PORT')){ if (key.endsWith('_PORT')) {
data=parseInt(data) data = parseInt(data)
} }
let curr=conf; let curr = conf;
const paths=key.split('_').map(p=>p.toLowerCase()) const paths = key.split('_').map(p => p.toLowerCase())
const last=paths.pop() const last = paths.pop()
for (const path of paths){ for (const path of paths) {
curr=curr[path] curr = curr[path]
} }
if(curr){ if (curr) {
console.log(`config ${paths.join('.')}.${last} to ${data}`); console.log(`config ${paths.join('.')}.${last} to ${data}`);
curr[last]=data curr[last] = data
} }
}) })
return conf return conf
} }
module.exports = { module.exports = {
inject_env_config inject_env_config
} }

View File

@@ -5,16 +5,16 @@
// --------------------------- // // --------------------------- //
window.tlrtcfile = { window.tlrtcfile = {
addUrlHashParams : function(obj){ addUrlHashParams: function (obj) {
let redirect = window.location.protocol + "//" + window.location.host + "#" let redirect = window.location.protocol + "//" + window.location.host + "#"
let oldObj = this.getRequestHashArgsObj(); let oldObj = this.getRequestHashArgsObj();
obj = Object.assign(oldObj,obj); obj = Object.assign(oldObj, obj);
for(let key in obj){ for (let key in obj) {
redirect += key + "=" + obj[key] + "&"; redirect += key + "=" + obj[key] + "&";
} }
return redirect; return redirect;
}, },
getRequestHashArgsObj : function () { getRequestHashArgsObj: function () {
let query = decodeURIComponent(window.location.hash.substring(1)); let query = decodeURIComponent(window.location.hash.substring(1));
let args = query.split("&"); let args = query.split("&");
let obj = {}; let obj = {};
@@ -22,18 +22,18 @@ window.tlrtcfile = {
let pair = args[i].split("="); let pair = args[i].split("=");
const key = pair[0]; const key = pair[0];
const val = pair[1]; const val = pair[1];
if(key){ if (key) {
obj[key] = val obj[key] = val
} }
} }
return obj; return obj;
}, },
getRequestHashArgs : function (key) { getRequestHashArgs: function (key) {
let query = decodeURIComponent(window.location.hash.substring(1)); let query = decodeURIComponent(window.location.hash.substring(1));
let args = query.split("&"); let args = query.split("&");
for (let i = 0; i < args.length; i++) { for (let i = 0; i < args.length; i++) {
let pair = args[i].split("="); let pair = args[i].split("=");
if(pair[0] === key){ if (pair[0] === key) {
return pair[1]; return pair[1];
} }
} }
@@ -163,7 +163,7 @@ window.tlrtcfile = {
}, 32) }, 32)
}, },
loadJS: function (url, callback) { loadJS: function (url, callback) {
var script = document.createElement('script'), let script = document.createElement('script'),
fn = callback || function () { }; fn = callback || function () { };
script.type = 'text/javascript'; script.type = 'text/javascript';
//IE //IE
@@ -189,7 +189,7 @@ window.tlrtcfile = {
); );
}, },
chatKeydown: function (dom, callback) { chatKeydown: function (dom, callback) {
if(dom){ if (dom) {
dom.onkeydown = function (e) { dom.onkeydown = function (e) {
if (e.defaultPrevented) { if (e.defaultPrevented) {
return; return;
@@ -198,7 +198,7 @@ window.tlrtcfile = {
if (e.shiftKey) { if (e.shiftKey) {
// shift+enter 换行 // shift+enter 换行
return; return;
}else if (e.key !== undefined) { } else if (e.key !== undefined) {
if (e.key === "Enter") { if (e.key === "Enter") {
// enter键执行 // enter键执行
callback && callback() callback && callback()
@@ -216,7 +216,8 @@ window.tlrtcfile = {
}, },
supposeWebrtc: function (rtcConfig) { supposeWebrtc: function (rtcConfig) {
try { try {
let testRTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.RTCIceGatherer; let testRTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection ||
window.mozRTCPeerConnection || window.RTCIceGatherer;
if (testRTCPeerConnection) { if (testRTCPeerConnection) {
new RTCPeerConnection(rtcConfig); new RTCPeerConnection(rtcConfig);
return true; return true;
@@ -228,7 +229,146 @@ window.tlrtcfile = {
return false; return false;
} }
}, },
checkWebRtcNATType: async function (rtcConfig) {
// 确认网络连接状态
try {
const rtcConnect = new RTCPeerConnection(rtcConfig);
const dataChannel = rtcConnect.createDataChannel('ping'); // 创建数据通道
// 生成本地候选者
const localCandidates = [];
const candidates = {};
rtcConnect.onicecandidate = (event) => {
if(!event.candidate){//等待几秒钟没有候选者了,可以初步判断结果了
rtcConnect.close();
return
}
localCandidates.push(event.candidate.candidate);
// if (event.candidate.candidate.indexOf('srflx') === -1) return;
// let fields;
// if (event.candidate.candidate.indexOf('a=candidate:') === 0) {
// fields = event.candidate.candidate.substring(12).split(' ');
// } else {
// fields = event.candidate.candidate.substring(10).split(' ');
// }
// let candidateFields = {
// ip: fields[4],
// port: parseInt(fields[5], 10),
// type: fields[7]
// };
// for (let i = 8; i < fields.length; i += 2) {
// if(fields[i] === 'raddr'){
// candidateFields.relatedAddress = fields[i + 1];
// }
// if(fields[i] === 'rport'){
// candidateFields.relatedPort = parseInt(fields[i + 1], 10);
// }
// if(fields[i] === 'tcptype'){
// candidateFields.tcpType = fields[i + 1];
// }
// }
// console.log("candidate : ",candidateFields);
// if (!candidates[candidateFields.relatedPort]){
// candidates[candidateFields.relatedPort] = []
// }
// candidates[candidateFields.relatedPort].push(candidateFields.port);
};
await rtcConnect.setLocalDescription(await rtcConnect.createOffer({
iceRestart: true // 避免使用缓存的ICE候选者
}));
// 等待一段时间以获取完整的ICE候选者
await new Promise((resolve) => setTimeout(resolve, 2000));
// 模拟将本地候选者发送给Server并从Server获取Server的候选者
const serverCandidates = []; // 假设这里填入从Server获取的候选者
const remoteCandidates = localCandidates.concat(serverCandidates);
// 解析ICE候选者中的IP和Port
const candidateIPandPorts = remoteCandidates.map( candidate => {
const fields = candidate.split(' ');
let candidateFields = {
ip: fields[4],
port: parseInt(fields[5], 10),
type: fields[7]
};
if (candidate.indexOf('srflx') !== -1){
let srflxFiled = ""
if (candidate.indexOf('a=candidate:') === 0) {
srflxFiled = candidate.substring(12).split(' ');
} else {
srflxFiled = candidate.substring(10).split(' ');
}
for (let i = 8; i < srflxFiled.length; i += 2) {
if(srflxFiled[i] === 'raddr'){
candidateFields.relatedAddress = srflxFiled[i + 1];
}
if(srflxFiled[i] === 'rport'){
candidateFields.relatedPort = parseInt(srflxFiled[i + 1], 10);
}
if(srflxFiled[i] === 'tcptype'){
candidateFields.tcpType = srflxFiled[i + 1];
}
}
}
return candidateFields;
});
console.log("candidateIPandPorts : ",candidateIPandPorts)
// 判断网络状态是否正常
const isNetworkBlocked = candidateIPandPorts.length === 0;
if (isNetworkBlocked) {
return "udp-blocked";
}
// 判断是否在NAT之后比较第一个候选者的IP和Port
const isBehindNAT = candidateIPandPorts[0].ip !== candidateIPandPorts[candidateIPandPorts.length - 1].ip ||
candidateIPandPorts[0].port !== candidateIPandPorts[candidateIPandPorts.length - 1].port;
if (!isBehindNAT) {
// 没有经过NAT转换可能存在网络防火墙
// TODO: 进行更多验证例如尝试连接不同的端口和IP
return "no-nat";
} else {
// 判断是否是端口限制型NAT
const isPortRestrictedNAT = candidateIPandPorts.some((candidate, index) => {
const nextCandidate = candidateIPandPorts[index + 1];
if (nextCandidate) {
return candidate.ip === nextCandidate.ip && candidate.port !== nextCandidate.port;
}
return false;
});
if (isPortRestrictedNAT) return 'port-restricted-nat';
// 判断是否是IP限制型NAT
const isIPRestrictedNAT = candidateIPandPorts.some((candidate, index) => {
const nextCandidate = candidateIPandPorts[index + 1];
if (nextCandidate) {
return candidate.ip !== nextCandidate.ip && candidate.port === nextCandidate.port;
}
return false;
});
if (isIPRestrictedNAT) return 'ip-restricted-nat';
return 'unknown-nat';
}
} catch (error) {
console.error('Error occurred:', error);
return 'error';
}
},
getWebrtcStats: async function (peerConnection) { getWebrtcStats: async function (peerConnection) {
if (!peerConnection) { if (!peerConnection) {
return "RTCPeerConnection is not available"; return "RTCPeerConnection is not available";
@@ -298,7 +438,7 @@ window.tlrtcfile = {
copyTxt: function (id, content) { copyTxt: function (id, content) {
let that = this; let that = this;
document.querySelector("#" + id).setAttribute("data-clipboard-text", content); document.querySelector("#" + id).setAttribute("data-clipboard-text", content);
var clipboard = new ClipboardJS('#' + id); let clipboard = new ClipboardJS('#' + id);
clipboard.on('success', function (e) { clipboard.on('success', function (e) {
e.clearSelection(); e.clearSelection();
if (window.layer) { if (window.layer) {
@@ -306,17 +446,17 @@ window.tlrtcfile = {
} }
}); });
}, },
getQrCode : function(id, content){ getQrCode: function (id, content) {
const qrcode = new QRCode(id, { const qrcode = new QRCode(id, {
text: content, // 二维码内容 text: content, // 二维码内容
width: 300, width: 300,
height: 300, height: 300,
colorDark : "#000000", // 码的颜色 colorDark: "#000000", // 码的颜色
colorLight : "#ffffff", // 码的背景色 colorLight: "#ffffff", // 码的背景色
correctLevel : QRCode.CorrectLevel.H // 高度容错 correctLevel: QRCode.CorrectLevel.H // 高度容错
}); });
}, },
getOpEventData: function(callback){ getOpEventData: function (callback) {
window.addEventListener('click', function (event) { window.addEventListener('click', function (event) {
callback("click", event) callback("click", event)
}) })
@@ -330,10 +470,10 @@ window.tlrtcfile = {
window.addEventListener('mousedown', function () { window.addEventListener('mousedown', function () {
callback("mousedown", null) callback("mousedown", null)
}) })
window.addEventListener('mouseup', function() { window.addEventListener('mouseup', function () {
callback("mouseup", null) callback("mouseup", null)
}) })
window.addEventListener('wheel', function(event) { window.addEventListener('wheel', function (event) {
callback("wheel", event) callback("wheel", event)
}) })
window.addEventListener('keydown', function (event) { window.addEventListener('keydown', function (event) {
@@ -379,8 +519,8 @@ window.tlrtcfile = {
function animateMargin() { function animateMargin() {
currentTime += increment; currentTime += increment;
let val = easeOutCubic(currentTime / duration) * change; let val = easeOutCubic(currentTime / duration) * change;
dom.style.marginLeft = val+"%"; dom.style.marginLeft = val + "%";
if(val === 100){ if (val === 100) {
callback() callback()
} }
if (currentTime < duration) { if (currentTime < duration) {
@@ -476,7 +616,7 @@ window.tlrtcfile = {
html = content; html = content;
} else { } else {
lang = file.name.split(".").pop(); lang = file.name.split(".").pop();
if(lang === 'log'){lang = 'txt'} if (lang === 'log') { lang = 'txt' }
html = hljs.highlight(content, { language: lang }).value; html = hljs.highlight(content, { language: lang }).value;
} }
@@ -493,7 +633,7 @@ window.tlrtcfile = {
<pre><code class="hljs ${lang}">${html}</code></pre> <pre><code class="hljs ${lang}">${html}</code></pre>
</div> </div>
`, `,
area: ["80%","80%"], area: ["80%", "80%"],
success: function (layero, index) { success: function (layero, index) {
document.querySelector(".layui-layer-content").style.borderRadius = "15px" document.querySelector(".layui-layer-content").style.borderRadius = "15px"
}, },
@@ -519,34 +659,34 @@ window.tlrtcfile = {
content: ` <div id="tl-rtc-file-pdf-container" style="height: 100%;"> </div> `, content: ` <div id="tl-rtc-file-pdf-container" style="height: 100%;"> </div> `,
success: function (layero, index) { success: function (layero, index) {
document.querySelector(".layui-layer-content").style.borderRadius = "15px" document.querySelector(".layui-layer-content").style.borderRadius = "15px"
try{ try {
let fileReader = new FileReader(); let fileReader = new FileReader();
fileReader.onload = function(e) { fileReader.onload = function (e) {
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.min.js'; pdfjsLib.GlobalWorkerOptions.workerSrc = './pdf.worker.min.js';
pdfjsLib.getDocument(e.target.result).promise.then(async function(pdf) { pdfjsLib.getDocument(e.target.result).promise.then(async function (pdf) {
function renderPage(numPages, num){ function renderPage(numPages, num) {
if(num - 1 >= numPages){ if (num - 1 >= numPages) {
return callback && callback("pdf加载渲染完毕") return callback && callback("pdf加载渲染完毕")
} }
pdf.getPage(num).then(function(page) { pdf.getPage(num).then(function (page) {
const viewport = page.getViewport({scale: 1.5}); const viewport = page.getViewport({ scale: 1.5 });
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
let pdfDom = document.getElementById("tl-rtc-file-pdf-container"); let pdfDom = document.getElementById("tl-rtc-file-pdf-container");
if(pdfDom){ if (pdfDom) {
pdfDom.appendChild(canvas); pdfDom.appendChild(canvas);
canvas.setAttribute("style","height: auto; width: 100%;") canvas.setAttribute("style", "height: auto; width: 100%;")
canvas.height = viewport.height; canvas.height = viewport.height;
canvas.width = viewport.width; canvas.width = viewport.width;
page.render({ page.render({
canvasContext: canvas.getContext('2d'), canvasContext: canvas.getContext('2d'),
viewport: viewport viewport: viewport
}).promise.then(function(e) { }).promise.then(function (e) {
let dom = document.querySelector(".layui-layer-title"); let dom = document.querySelector(".layui-layer-title");
if(dom){ if (dom) {
dom.innerText = `${numPages}页 - 已渲染${num}`; dom.innerText = `${numPages}页 - 已渲染${num}`;
} }
renderPage(numPages, num+1) renderPage(numPages, num + 1)
}); });
} }
}) })
@@ -555,13 +695,13 @@ window.tlrtcfile = {
}); });
} }
fileReader.readAsDataURL(file); fileReader.readAsDataURL(file);
}catch(e){ } catch (e) {
return callback && callback("加载预览pdf资源失败"); return callback && callback("加载预览pdf资源失败");
} }
} }
}); });
}, },
previewWordFile : function (options) { previewWordFile: function (options) {
let { file, max, callback } = options; let { file, max, callback } = options;
if (file.size > max) { if (file.size > max) {
@@ -570,7 +710,7 @@ window.tlrtcfile = {
layer.open({ layer.open({
type: 1, type: 1,
content : ` content: `
<div style="width:100%;height:100%;" id="tl-rtc-file-word"></div> <div style="width:100%;height:100%;" id="tl-rtc-file-word"></div>
`, `,
area: ["80%", "80%"], area: ["80%", "80%"],
@@ -578,15 +718,15 @@ window.tlrtcfile = {
success: function (layero, index) { success: function (layero, index) {
document.querySelector(".layui-layer-content").style.borderRadius = "15px" document.querySelector(".layui-layer-content").style.borderRadius = "15px"
let reader = new FileReader(); let reader = new FileReader();
reader.onload = function(event) { reader.onload = function (event) {
let blob = new Blob([event.target.result], {type: file.type}); let blob = new Blob([event.target.result], { type: file.type });
docx.renderAsync(blob, document.getElementById("tl-rtc-file-word")) docx.renderAsync(blob, document.getElementById("tl-rtc-file-word"))
}; };
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}, },
}); });
}, },
previewExcelFile : function (options) { previewExcelFile: function (options) {
let { file, max, callback } = options; let { file, max, callback } = options;
if (file.size > max) { if (file.size > max) {
@@ -647,7 +787,7 @@ window.tlrtcfile = {
<img src="${reader.result}" style="max-width: 98%; max-height: 98%; margin-left: 1%; margin-top: 1%;"> <img src="${reader.result}" style="max-width: 98%; max-height: 98%; margin-left: 1%; margin-top: 1%;">
</div> </div>
`, `,
success : function(){ success: function () {
document.querySelector(".layui-layer-content").style.borderRadius = "15px"; document.querySelector(".layui-layer-content").style.borderRadius = "15px";
} }
}); });

View File

@@ -19,7 +19,9 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
data: function () { data: function () {
let socket = null; let socket = null;
if (io) { if (io) {
socket = io(resData.wsHost); socket = io(resData.wsHost,{
transports : ['polling', 'websocket']
});
} }
return { return {
langMode : "zh", // 默认中文 langMode : "zh", // 默认中文
@@ -424,7 +426,7 @@ axios.get("/api/comm/initData?turn="+useTurn, {}).then((initData) => {
return ['completed', 'connected', 'checking', 'new'].includes(state); return ['completed', 'connected', 'checking', 'new'].includes(state);
}, },
consoleLogo : function(){ consoleLogo : function(){
window.console.log(`%c____ TL-RTC-FILE-V${this.version} ____ \n____ FORK ME IN GITHUB ____ \n____ https://github.com/tl-open-source/tl-rtc-file ____`, this.logo) window.console.log(`%c____ TL-RTC-FILE-V${this.version} ____ \n____ FORK ME ON GITHUB ____ \n____ https://github.com/tl-open-source/tl-rtc-file ____`, this.logo)
}, },
changeLanguage: function () { changeLanguage: function () {
let that = this; let that = this;

View File

@@ -1,7 +1,8 @@
const express = require("express"); const express = require("express");
const fs = require('fs'); const fs = require('fs');
const https = require('https'); const https = require('https');
const conf = require("./conf/cfg.json"); const { inject_env_config } = require("./conf/env_config");
const conf = inject_env_config(require("./conf/cfg.json"));
const fileApiRouters = require("./src/controller/router")(); const fileApiRouters = require("./src/controller/router")();
const db = require("./src/tables/db"); const db = require("./src/tables/db");
const utils = require("./src/utils/utils"); const utils = require("./src/utils/utils");

View File

@@ -2,7 +2,8 @@ const https = require('https');
const socketIO = require('socket.io'); const socketIO = require('socket.io');
const fs = require('fs'); const fs = require('fs');
const db = require("./src/tables/db"); const db = require("./src/tables/db");
const conf = require("./conf/cfg.json"); const { inject_env_config } = require("./conf/env_config");
const conf = inject_env_config(require("./conf/cfg.json"));
const socket = require("./src/socket/index") const socket = require("./src/socket/index")
const utils = require("./src/utils/utils"); const utils = require("./src/utils/utils");

View File

@@ -11,7 +11,7 @@ function initData(req, res) {
//是否开启turn //是否开启turn
const openTurn = (req.query.turn || "") === 'true'; const openTurn = (req.query.turn || "") === 'true';
//使用的账号模式, true : 有效账号模式, false : 固定账号 //使用的账号模式, true : 有效账号模式, false : 固定账号
const useSecret = (req.query.secret || "") === 'true'; const useSecret = (req.query.secret || "") === 'true' || true;
//ice服务器配置 //ice服务器配置
const iceServers = utils.genTurnServerIceServersConfig(openTurn, useSecret, "tlrtcfile"); const iceServers = utils.genTurnServerIceServersConfig(openTurn, useSecret, "tlrtcfile");

View File

@@ -1,6 +1,6 @@
const os = require('os'); const os = require('os');
const crypto = require('crypto');
const cfg = require('./../../conf/cfg.json'); const cfg = require('./../../conf/cfg.json');
const crypto = require('crypto');
/** /**
* 获取本机ip * 获取本机ip
@@ -208,7 +208,7 @@ function genTurnServerIceServersConfig(withTurn, useSecret, username){
} }
// 有效账号模式 // 有效账号模式
const secret = cfg.webrtc.turn.secret || "tlrtcfile"; const secret = cfg.webrtc.turn.secret;
//生成账号的有效期 //生成账号的有效期
const expirseTime = 60 * 60 * 24 * 1000; const expirseTime = 60 * 60 * 24 * 1000;
//当前时间 //当前时间
@@ -222,7 +222,7 @@ function genTurnServerIceServersConfig(withTurn, useSecret, username){
iceServers.push({ iceServers.push({
urls : cfg.webrtc.turn.host, urls : cfg.webrtc.turn.host,
username: turnUsername, username: turnUsername,
credential: turnPassword credential: turnPassword,
}) })
return iceServers; return iceServers;

View File

@@ -1,5 +0,0 @@
pm2 start npm --name=tl-rtc-file-api-server -- run sapi
sleep 1
pm2 start npm --name=tl-rtc-file-socket-server -- run ssocket