Merge branch 'hotfix/v1.1.1' into hotfix/campaigns-banner

This commit is contained in:
Alexey Safronov 2022-02-03 21:05:07 +03:00
commit 3a16fdccfd
69 changed files with 2792 additions and 2841 deletions

View File

@ -40,7 +40,7 @@ using ASC.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using Newtonsoft.Json;
namespace ASC.Core.Notify.Signalr
{
@ -308,18 +308,54 @@ namespace ASC.Core.Notify.Signalr
ProcessError(error);
}
}
public void FilesChangeEditors(int tenantId, string fileId, bool finish)
public void StartEdit<T>(T fileId, string room)
{
try
{
MakeRequest("changeEditors", new { tenantId, fileId, finish });
MakeRequest("start-edit", new { room, fileId });
}
catch (Exception error)
{
ProcessError(error);
}
}
}
public void StopEdit<T>(T fileId, string room)
{
try
{
MakeRequest("stop-edit", new { room, fileId });
}
catch (Exception error)
{
ProcessError(error);
}
}
public void CreateFile<T>(T fileId, string room, string data)
{
try
{
MakeRequest("create-file", new { room, fileId, data });
}
catch (Exception error)
{
ProcessError(error);
}
}
public void DeleteFile<T>(T fileId, string room)
{
try
{
MakeRequest("delete-file", new { room, fileId });
}
catch (Exception error)
{
ProcessError(error);
}
}
public T GetAgent<T>(string numberId, List<Guid> contactsResponsibles)
{
@ -370,8 +406,8 @@ namespace ASC.Core.Notify.Signalr
webClient.Headers.Add("Authorization", CreateAuthToken());
webClient.Headers[HttpRequestHeader.ContentType] = "application/json";
return webClient.UploadString(GetMethod(method), jsonData);
}
}
private T MakeRequest<T>(string method, object data)
{
var resultMakeRequest = MakeRequest(method, data);

View File

@ -1,145 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<Name>ASC.Socket.IO</Name>
<RootNamespace>ASC.Socket.IO</RootNamespace>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>325ae82f-b416-4f29-94df-c5b18fc4926d</ProjectGuid>
<ProjectHome>.</ProjectHome>
<StartupFile>app.js</StartupFile>
<SearchPath>
</SearchPath>
<WorkingDirectory>.</WorkingDirectory>
<OutputPath>.</OutputPath>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ProjectTypeGuids>{3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD}</ProjectTypeGuids>
<ProjectView>ShowAllFiles</ProjectView>
<NodejsPort>3000</NodejsPort>
<StartWebBrowser>true</StartWebBrowser>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
</PropertyGroup>
<ItemGroup>
<Compile Include="app.js" />
<Compile Include="app\apiRequestManager.js" />
<Compile Include="app\controllers\chat.js" />
<Compile Include="app\controllers\counters.js" />
<Compile Include="app\controllers\files.js" />
<Compile Include="app\controllers\index.js" />
<Compile Include="app\controllers\mail.js" />
<Compile Include="app\controllers\voip.js" />
<Compile Include="app\hubs\chat.js" />
<Compile Include="app\hubs\counters.js" />
<Compile Include="app\hubs\files.js" />
<Compile Include="app\hubs\voip.js" />
<Compile Include="app\hubs\voipListPhones.js" />
<Compile Include="app\hubs\voipPhone.js" />
<Compile Include="app\log.js" />
<Compile Include="app\middleware\auth.js" />
<Compile Include="app\middleware\authService.js" />
<Compile Include="app\portalManager.js" />
<Compile Include="config\index.js" />
<Compile Include="bin\www" />
<Content Include="config\config.json" />
<Content Include="package.json" />
<Content Include="README.md" />
<Content Include="typings.json" />
</ItemGroup>
<ItemGroup>
<Folder Include="app\middleware\" />
<Folder Include="app\hubs\" />
<Folder Include="app\controllers\" />
<Folder Include="bin\" />
<Folder Include="app\" />
<Folder Include="config\" />
<Folder Include="public\" />
<Folder Include="public\images\" />
<Folder Include="public\javascripts\" />
<Folder Include="public\stylesheets\" />
<Folder Include="typings\" />
<Folder Include="typings\globals\" />
<Folder Include="typings\globals\body-parser\" />
<Folder Include="typings\globals\cookie-parser\" />
<Folder Include="typings\globals\debug\" />
<Folder Include="typings\globals\express-serve-static-core\" />
<Folder Include="typings\globals\express\" />
<Folder Include="typings\globals\jade\" />
<Folder Include="typings\globals\mime\" />
<Folder Include="typings\globals\moment\" />
<Folder Include="typings\globals\morgan\" />
<Folder Include="typings\globals\node\" />
<Folder Include="typings\globals\serve-favicon\" />
<Folder Include="typings\globals\serve-static\" />
<Folder Include="typings\globals\socket.io\" />
<Folder Include="typings\globals\stylus\" />
<Folder Include="views\" />
</ItemGroup>
<ItemGroup>
<TypeScriptCompile Include="typings\globals\body-parser\index.d.ts" />
<TypeScriptCompile Include="typings\globals\cookie-parser\index.d.ts" />
<TypeScriptCompile Include="typings\globals\debug\index.d.ts" />
<TypeScriptCompile Include="typings\globals\express-serve-static-core\index.d.ts" />
<TypeScriptCompile Include="typings\globals\express\index.d.ts" />
<TypeScriptCompile Include="typings\globals\jade\index.d.ts" />
<TypeScriptCompile Include="typings\globals\mime\index.d.ts" />
<TypeScriptCompile Include="typings\globals\moment\index.d.ts" />
<TypeScriptCompile Include="typings\globals\morgan\index.d.ts" />
<TypeScriptCompile Include="typings\globals\node\index.d.ts" />
<TypeScriptCompile Include="typings\globals\serve-favicon\index.d.ts" />
<TypeScriptCompile Include="typings\globals\serve-static\index.d.ts" />
<TypeScriptCompile Include="typings\globals\socket.io\index.d.ts" />
<TypeScriptCompile Include="typings\globals\stylus\index.d.ts" />
<TypeScriptCompile Include="typings\index.d.ts" />
</ItemGroup>
<!-- Do not delete the following Import Project. While this appears to do nothing it is a marker for setting TypeScript properties before our import that depends on them. -->
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.TypeScript.targets" Condition="False" />
<Import Project="$(VSToolsPath)\Node.js Tools\Microsoft.NodejsTools.targets" />
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>0</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:48022/</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://localhost:1337</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}" User="">
<WebProjectProperties>
<StartPageUrl>
</StartPageUrl>
<StartAction>CurrentPage</StartAction>
<AspNetDebugging>True</AspNetDebugging>
<SilverlightDebugging>False</SilverlightDebugging>
<NativeDebugging>False</NativeDebugging>
<SQLDebugging>False</SQLDebugging>
<ExternalProgram>
</ExternalProgram>
<StartExternalURL>
</StartExternalURL>
<StartCmdLineArguments>
</StartCmdLineArguments>
<StartWorkingDirectory>
</StartWorkingDirectory>
<EnableENC>False</EnableENC>
<AlwaysStartWebServerOnDebug>False</AlwaysStartWebServerOnDebug>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@ -1,92 +0,0 @@
const express = require('express');
const logger = require('morgan');
const cookieParser = require('cookie-parser');
const expressSession = require("express-session");
const sharedsession = require("express-socket.io-session");
const RedisStore = require('connect-redis')(expressSession);
const MemoryStore = require('memorystore')(expressSession);
const config = require('./config');
const winston = require('./app/log.js');
const app = express();
const secret = config.get("core.machinekey") + (new Date()).getTime();
const secretCookieParser = cookieParser(secret);
const baseCookieParser = cookieParser();
winston.stream = {
write: (message) => winston.info(message)
};
const redis = {
host: config.get("redis:host"),
port: config.get("redis:port"),
ttl: 3600
}
let store;
if(redis.host && redis.port){
store = new RedisStore(redis);
} else {
store = new MemoryStore();
}
const session = expressSession({
store: store,
secret: secret,
resave: true,
saveUninitialized: true,
cookie: {
path: '/',
httpOnly: true,
secure: false,
maxAge: null
},
cookieParser: secretCookieParser,
name: "socketio.sid"
});
app.set('port', config.get('port') || 3000);
app.use(logger("dev", { "stream": winston.stream }));
app.use(session);
app.get('/', (req, res) => { res.send('<h1>Hello world</h1>'); });
const server = app.listen(app.get('port'), () => {
//log.info('Express server listening on port ' + server.address().port);
});
const io = require('socket.io')(server, {
perMessageDeflate : false,
cookie: false,
handlePreflightRequest: function (req, res) {
session(req, res, ()=>{});
res.writeHead(200, {'Content-Type': 'text/html'});
res.end();
},
allowRequest : function(req, fn){
var cookies = baseCookieParser(req, null, ()=>{});
if(!req.cookies || (!req.cookies['asc_auth_key'] && !req.cookies['authorization'])){
return fn('auth', false);
}
return io.checkRequest(req, fn);
}
});
const auth = require('./app/middleware/auth.js');
io
.use(sharedsession(session, secretCookieParser, {autoSave: true}))
.use((socket, next) => {
baseCookieParser(socket.client.request, null, next);
})
.use((socket, next) => {
auth(socket, next);
});
const countersHub = require('./app/hubs/counters.js')(io);
const voipHub = require('./app/hubs/voip.js')(io);
const chatHub = require('./app/hubs/chat.js')(io);
const filesHub = require('./app/hubs/files.js')(io);
app.use("/controller", require('./app/controllers')(countersHub, chatHub, voipHub, filesHub));
module.exports = app;

View File

@ -1,147 +0,0 @@
const apiBasePath = "/api/2.0/",
portalManager = require('./portalManager.js'),
request = require('request'),
log = require("./log.js");
function makeRequest(apiMethod, req, options, onSuccess){
makeHeaders(req, options);
options.uri = getBasePath(req) + apiMethod;
log.info(options.uri);
return new Promise((resolve, reject) => {
request(options, (error, response, body) => {
let result = {};
try {
result = typeof body === "string" ? JSON.parse(body) : body;
} catch (err) {
log.error(options.uri, err);
}
error = checkError(error, response, result);
if(error) {
log.error(options.uri, error);
if (error == 401 && req.session) {
req.session.destroy(() => reject(error));
return
} else {
reject(error);
return;
}
}
resolve(onSuccess(result));
});
});
}
function makeHeaders(req, options) {
options.headers = {};
if (req.cookies && req.cookies['asc_auth_key']) {
options.headers["Authorization"] = req.cookies['asc_auth_key'];
}
if (req.headers) {
const xRewriterUrlHeader = 'x-rewriter-url',
xForwardedForHeader = 'x-forwarded-for';
if (req.headers[xRewriterUrlHeader]) {
options.headers[xRewriterUrlHeader] = req.headers[xRewriterUrlHeader];
}
if (req.headers[xForwardedForHeader]) {
options.headers[xForwardedForHeader] = req.headers[xForwardedForHeader];
}
}
}
function getBasePath(req) {
return portalManager(req).replace(/\/$/g, '') + apiBasePath;
}
function checkError(error, response, result) {
if (error) {
return error;
}
if (result && result.error && result.error.message) {
return result.error.message;
}
if (response.statusCode > 400) {
return response.statusCode;
}
}
class RequestManager {
constructor() { }
makeRequest(apiMethod, req, options) {
return makeRequest(apiMethod, req, options, (result) => {
return typeof result.response !== "undefined" ? result.response : result;
});
}
get(apiMethod, req, body) {
if (body) {
apiMethod = `${apiMethod}?`;
for (const item in body) {
if (body.hasOwnProperty(item)) {
apiMethod = `${apiMethod}${item}=${body[item]}&`;
}
}
apiMethod = apiMethod.substring(0, apiMethod.length - 1);
}
return this.makeRequest(apiMethod, req, { method: "GET" });
}
post(apiMethod, req, body) {
return this.makeRequest(apiMethod, req, { method: "POST", body, json: true });
}
put(apiMethod, req, body) {
return this.makeRequest(apiMethod, req, { method: "PUT", body, json: true });
}
dlt(apiMethod, req, body) {
const options = { method: "DELETE" };
if (typeof body !== "undefined") {
options.body = req.body;
options.json = true;
}
return this.makeRequest(apiMethod, req, options);
}
batch(batchMethod, req){
const options = {
method: "POST",
form:{ batch: JSON.stringify(batchMethod.methods) },
json: true
};
return makeRequest("batch.json", req, options, (result) => {
const resultResponse = result.response;
if(result.response !== "undefined" && Array.isArray(result.response)) {
let data = [];
for(let i = 0, j = resultResponse.length; i < j; i++){
const dataItem = typeof resultResponse[i].data === "string" ? JSON.parse(resultResponse[i].data) : resultResponse[i].data
data.push(dataItem.response);
}
return data;
}
return result;
});
}
batchFactory(){
return new batchFactory();
}
}
class batchFactory {
constructor() {
this.methods = [];
}
get(url) {
this.methods.push({method: "get", RelativeUrl: `${apiBasePath}${url}`});
return this;
}
}
module.exports = new RequestManager();

View File

@ -1,23 +0,0 @@
module.exports = (chat) => {
const router = require('express').Router();
router
.post("/send", (req, res) => {
chat.send(req.body);
res.end();
})
.post("/sendInvite", (req, res) => {
chat.sendInvite(req.body);
res.end();
})
.post("/setState", (req, res) => {
chat.setState(req.body);
res.end();
})
.post("/sendOfflineMessages", (req, res) => {
chat.sendOfflineMessages(req.body);
res.end();
});
return router;
}

View File

@ -1,15 +0,0 @@
module.exports = (counters) => {
const router = require('express').Router();
router
.post("/sendUnreadUsers", (req, res) => {
counters.sendUnreadUsers(req.body);
res.end();
})
.post("/sendUnreadCounts", (req, res) => {
counters.sendUnreadCounts(req.body);
res.end();
});
return router;
}

View File

@ -1,11 +1,25 @@
module.exports = (files) => {
const router = require('express').Router();
const router = require("express").Router();
router
.post("/changeEditors", (req, res) => {
files.changeEditors(req.body);
res.end();
});
router.post("/start-edit", (req, res) => {
files.startEdit(req.body);
res.end();
});
return router;
};
router.post("/stop-edit", (req, res) => {
files.stopEdit(req.body);
res.end();
});
router.post("/create-file", (req, res) => {
files.createFile(req.body);
res.end();
});
router.post("/delete-file", (req, res) => {
files.deleteFile(req.body);
res.end();
});
return router;
};

View File

@ -1,26 +1,22 @@
module.exports = (counters, chat, voip, files) => {
const router = require('express').Router(),
bodyParser = require('body-parser'),
authService = require('../middleware/authService.js')();
module.exports = (files) => {
const router = require("express").Router(),
bodyParser = require("body-parser"),
check = require("../middleware/authService.js")();
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: false }));
router.use(require('cookie-parser')());
router.use((req, res, next) => {
if (!authService(req)) {
res.sendStatus(403);
return;
}
next();
});
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: false }));
router.use(require("cookie-parser")());
router.use((req, res, next) => {
const token = req?.headers?.authorization;
if (!check(token)) {
res.sendStatus(403);
return;
}
router
.use("/counters", require(`./counters.js`)(counters))
.use("/mail", require(`./mail.js`)(counters))
.use("/chat", require(`./chat.js`)(chat))
.use("/voip", require(`./voip.js`)(voip))
.use("/files", require(`./files.js`)(files));
next();
});
return router;
}
router.use("/files", require(`./files.js`)(files));
return router;
};

View File

@ -1,15 +0,0 @@
module.exports = (counters) => {
const router = require('express').Router();
router
.post("/updateFolders", (req, res) => {
counters.updateFolders(req.body);
res.end();
})
.post("/sendMailNotification", (req, res) => {
counters.sendMailNotification(req.body);
res.end();
});
return router;
}

View File

@ -1,32 +0,0 @@
module.exports = (voip) => {
const router = require('express').Router();
router
.post("/enqueue", (req, res) => {
const { numberId, callId, agent } = req.body;
voip.enqueue(numberId, callId, agent);
res.end();
})
.post("/incoming", (req, res) => {
const { callId, agent } = req.body;
voip.incoming(callId, agent);
res.end();
})
.post("/miss", (req, res) => {
const { numberId, callId, agent } = req.body;
voip.miss(numberId, callId, agent);
res.end();
})
.post("/getAgent", (req, res) => {
const { numberId, contactsResponsibles } = req.body;
const result = voip.getAgent(numberId, contactsResponsibles);
res.send(JSON.stringify({item1: result.result, item2: result.isAnyNotOffline}));
})
.post("/reload", (req, res) => {
const { numberRoom, agentId } = req.body;
voip.reload(numberRoom, agentId);
res.end();
});
return router;
}

View File

@ -1,219 +0,0 @@
module.exports = (io, countersHub) => {
const apiRequestManager = require('../apiRequestManager.js');
const co = require('co');
const baseTalkApiUrl = 'portal/talk/';
const chat = io.of("/chat");
const onlineUsers = [];
chat.on('connection', (socket) => {
const request = socket.client.request;
if(!request.user || !request.user.id) return;
const user = request.user;
const portal = request.portal;
const userId = user.id;
const userName = user.userName.toLowerCase();
const tenantId = portal.tenantId;
const tenantDomain = portal.tenantDomain;
socket
.on('disconnect', onDisconnectUser)
.on('connectUser', onConnectUser)
.on('disconnectUser', onDisconnectUser)
.on('send', onSend)
.on('getStates', onGetStates)
.on('getContactInfo', onGetContactInfo)
.on('getInitData', onGetInitData)
.on('sendTyping', onSendTyping)
.on('sendStateToTenant', onSendStateToTenant)
.on('getRecentMessages', onGetRecentMessages)
.on('chatPing', onPing);
function onDisconnectUser(change, fn) {
co(function *() {
let state;
if (!onlineUsers[tenantId]) return;
if (!onlineUsers[tenantId][userId] || !socket.onConnectUser) {
if(typeof fn == "function") fn();
return;
}
if (change === true || onlineUsers[tenantId][userId].counter === 1) {
delete onlineUsers[tenantId][userId];
state = yield apiRequestManager.dlt(`${baseTalkApiUrl}connection?connectionId=${userId}`, request);
socket.emit("disconnectUser");
} else {
onlineUsers[tenantId][userId].counter--;
state = yield apiRequestManager.get(`${baseTalkApiUrl}state`, request, { userName });
if (state !== 4) {
// setStatus
socket.broadcast.to(`${tenantId}-${userName}`).emit('setStatus', state);
}
}
socket.broadcast.to(tenantId).emit('setState', userName, state);
})
.catch((err)=>{
if(typeof fn == "function") fn(err);
});
}
function onConnectUser(state) {
socket.join([tenantId, `${tenantId}-${userName}`]);
socket.onConnectUser = true;
if (!onlineUsers[tenantId]) {
onlineUsers[tenantId] = {};
}
var counter = onlineUsers[tenantId][userId];
if (!counter) {
counter = onlineUsers[tenantId][userId] = { counter: 1 };
} else {
counter.counter++;
}
co(function* () {
if (counter.counter === 1) {
state = yield apiRequestManager.post(`${baseTalkApiUrl}connection`, request, { connectionId: userId, state });
} else {
state = yield apiRequestManager.post(`${baseTalkApiUrl}state`, request, { state });
if (state !== 4) {
socket.broadcast.to(`${tenantId}-${userName}`).emit('setStatus', state);
}
}
socket.broadcast.to(tenantId).emit('setState', userName, state, false);
chat.to(`${tenantId}-${userName}`).emit("connectUser");
})
.catch((err)=>{
chat.to(`${tenantId}-${userName}`).emit("connectUser", err);
});
}
function onSend(calleeUserName, messageText) {
co(function* () {
const message = { u: userName, t: messageText };
if (calleeUserName) {
chat.to(`${tenantId}-${calleeUserName}`).emit('send', message, calleeUserName);
socket.broadcast.to(`${tenantId}-${userName}`).emit('send', message, calleeUserName);
}
yield apiRequestManager.post(`${baseTalkApiUrl}message`, request, { to: calleeUserName, text: messageText });
});
}
function onGetStates(fn) {
apiRequestManager.get(`${baseTalkApiUrl}states`, request)
.then((result) => {
socket.emit('statesRetrieved', result);
})
.catch((err) => {
if (typeof fn === "function") fn(err);
});;
}
function onGetContactInfo(calleeUserName, fn) {
co(function* () {
const calleeUser = yield apiRequestManager.get('people/' + calleeUserName, request);
if (!calleeUser || calleeUser.id === "4A515A15-D4D6-4b8e-828E-E0586F18F3A3") throw "Can't get UserInfo";
const calleeUserState = yield apiRequestManager.get(`${baseTalkApiUrl}state`, request, { userName: calleeUserName });
if(typeof fn === "function") fn(calleeUserName, calleeUserState);
})
.catch((err) => {
if (err && typeof fn === "function") fn(null, null, err);
});
}
function onGetInitData(fn) {
co(function* () {
const states = yield apiRequestManager.get(`${baseTalkApiUrl}states`, request);
const users = yield apiRequestManager.get('people', request);
const result = users
.filter((item) => item.id !== userId)
.sort((item1, item2) => {
if (item1.displayName < item2.displayName) return -1;
if (item1.displayName > item2.displayName) return 1;
return 0;
})
.map((item) => {
const uName = item.userName.toLowerCase();
return { u: uName, d: item.displayName, s: states[uName] || 4}
});
socket.emit('initDataRetrieved', userName, user.displayName, result, tenantId, tenantDomain);
})
.catch((err) => {
if (typeof fn === "function") fn(err);
});
}
function onSendTyping(calleeUserName) {
chat.to(`${tenantId}-${calleeUserName}`).emit('sendTypingSignal', userName);
}
function onSendStateToTenant(state) {
apiRequestManager.post(`${baseTalkApiUrl}state`, request, { state })
.then((result) => {
socket.broadcast.to(tenantId).emit('setState', userName, result, false);
});
}
function onGetRecentMessages(calleeUserName, id, fn) {
apiRequestManager.get(`${baseTalkApiUrl}recentMessages`, request, { calleeUserName, id })
.then((recentMessages) => {
if (typeof fn === "function") fn(recentMessages);
})
.catch((err) => {
if (typeof fn === "function") fn(null, err);
});
}
function onPing(state) {
apiRequestManager.post(`${baseTalkApiUrl}ping`, request, { state: state });
}
});
function tenantPlusUserRoom(tenantId, userName) {
return chat.to(`${tenantId}-${userName}`);
}
function send({ tenantId, callerUserName, calleeUserName, message, isTenantUser } = {}) {
if (typeof tenantId === "undefined" || !calleeUserName || !message) {
return;
}
tenantPlusUserRoom(tenantId, calleeUserName).emit('send', message, calleeUserName, isTenantUser);
if (!isTenantUser) {
tenantPlusUserRoom(tenantId, callerUserName).emit('send', message, calleeUserName, isTenantUser);
}
}
function sendInvite({ tenantId, calleeUserName, message } = {}) {
if (typeof tenantId === "undefined" || !calleeUserName || !message) {
return;
}
tenantPlusUserRoom(tenantId, calleeUserName).emit('sendInvite', message);
}
function setState({ tenantId, from, state } = {}) {
if (typeof tenantId === "undefined" || !from) {
return;
}
chat.to(`${tenantId}`).emit('setState', from, state);
}
function sendOfflineMessages({ tenantId, callerUserName, users } = {}) {
if (typeof tenantId === "undefined" || !callerUserName || !users) {
return;
}
tenantPlusUserRoom(tenantId, callerUserName).emit('sendOfflineMessages', users);
}
return { send, sendInvite, setState, sendOfflineMessages };
}

View File

@ -1,209 +0,0 @@
module.exports = (io) => {
const apiRequestManager = require('../apiRequestManager.js');
const co = require('co');
const counters = io.of('/counters');
const onlineUsers = [];
const uaParser = require('ua-parser-js');
counters.on('connection', (socket) => {
const request = socket.client.request;
if(!request.user || !request.user.id) return;
const userId = request.user.id;
const tenantId = request.portal.tenantId;
let ipAddress = socket.handshake.headers['x-forwarded-for'];
const userAgent = socket.request.headers['user-agent'];
const parser = new uaParser();
parser.setUA(userAgent);
const [os, browser] = [parser.getOS(), parser.getBrowser()];
const operationSystem = os.version !== undefined ? `${os.name} ${os.version}` : `${os.name}`;
const browserVersion = browser.version ? browser.version : '';
ipAddress = getCleanIP(ipAddress);
const browserName = `${browser.name} ${browserVersion}`;
const userName = (request.user.userName || "").toLowerCase();
getCityByIP(ipAddress);
socket.join([tenantId, `${tenantId}-${userId}`, `${tenantId}-${userName}`]);
getNewMessagesCount();
socket
.on('disconnect', () => {
if (!onlineUsers[tenantId]) return;
if (!onlineUsers[tenantId][userId]) return;
if (!onlineUsers[tenantId][userId].browsers) return;
if (!onlineUsers[tenantId][userId].browsers[browserName]) return;
onlineUsers[tenantId][userId].browsers[browserName].counter--;
if (onlineUsers[tenantId][userId].browsers[browserName].counter === 0) {
delete onlineUsers[tenantId][userId].browsers[browserName];
}
if (Object.keys(onlineUsers[tenantId][userId].browsers).length === 0) {
delete onlineUsers[tenantId][userId];
counters.to(tenantId).emit('renderOfflineUser', userId);
updateMailUserActivity(socket.client.request, false);
console.log(`a user ${userName} in portal ${tenantId} disconnected`);
}
})
.on('renderOnlineUsers', () => {
counters.to(tenantId).emit('renderOnlineUsers', onlineUsers[tenantId] || []);
})
.on('sendMessagesCount', (count) => {
socket.broadcast.to(`${tenantId}-${userId}`).emit('sendMessagesCount', count);
})
.on('sendFeedsCount', () => {
socket.broadcast.to(`${tenantId}-${userId}`).emit('sendFeedsCount', 0);
})
.on('updateFolders', (shouldUpdateMailBox) => {
counters.in(`${tenantId}-${userId}`).clients((error, clients) => {
if (error) return;
if (clients.length > 1) {
getMessageCount().then((count) => {
socket.broadcast.to(`${tenantId}-${userId}`).emit('updateFolders', count, shouldUpdateMailBox);
});
}
});
});
function updateMailUserActivity(request, userOnline = true) {
if(!request.mailEnabled) return;
setTimeout(function(){
if((!userOnline && typeof onlineUsers[tenantId][userId] != "undefined") ||
(userOnline && !onlineUsers[tenantId][userId])) return;
apiRequestManager.put("mail/accounts/updateuseractivity.json", request, { userOnline });
console.log(`updateuseractivity ${userOnline}`);
}, 3000);
}
function getNewMessagesCount() {
co(function* () {
let mailMessageFolders = [], feedCount = 0, messageCount = 0, mailCount = 0;
var batchRequest = apiRequestManager.batchFactory()
.get("feed/newfeedscount.json")
.get("portal/talk/unreadmessages.json");
if(!request.mailEnabled){
[feedCount, messageCount] = yield apiRequestManager.batch(batchRequest,request);
}else{
[feedCount, messageCount, mailMessageFolders] = yield apiRequestManager.batch(batchRequest.get("mail/folders.json"), request);
}
mailCount = getInreadMessageCount(mailMessageFolders);
counters.to(`${tenantId}-${userId}`).emit('getNewMessagesCount',
{
me: messageCount,
f: feedCount,
ma: mailCount
});
})
.catch((err)=>{
});
}
function getMessageCount() {
if(!request.mailEnabled) return new Promise((resolve) => { resolve(0)});
return apiRequestManager.get("mail/folders.json", request).then(getInreadMessageCount);
}
function getCityByIP (ip){
apiRequestManager.get("portal/ip/" + ip, request, false)
.then((result) => {
const city = result.city;
console.log(`a user ${userName} in portal ${tenantId} connected, IP ${ipAddress}, city ${city}, OS ${operationSystem}, browser ${browserName}`);
console.log("-----");
if (!onlineUsers[tenantId]) {
onlineUsers[tenantId] = {};
}
if (!onlineUsers[tenantId][userId]) {
onlineUsers[tenantId][userId] = {browsers: {},FirstConnection: new Date(), LastConnection: new Date() };
socket.broadcast.to(tenantId).emit('renderOnlineUser', userId);
updateMailUserActivity(socket.client.request);
}
else {
onlineUsers[tenantId][userId].LastConnection = new Date();
}
if (!onlineUsers[tenantId][userId].browsers[browserName]) {
onlineUsers[tenantId][userId].browsers[browserName] = {counter:1, ipAddress: ipAddress, city: city, operationSystem: operationSystem };
} else {
onlineUsers[tenantId][userId].browsers[browserName].counter++;
}
})
.catch((err) => {
console.log(err);
});
};
function getCleanIP (ipAddress) {
const indexOfColon = ipAddress.indexOf(':');
if (indexOfColon === -1){
return ipAddress;
} else if (indexOfColon > 3){
return ipAddress.substring(0, indexOfColon);
}
else {
return "127.0.0.1";
}
}
});
function getInreadMessageCount(mailMessageFolders){
let mailMessageFoldersCount = mailMessageFolders.length;
while (mailMessageFoldersCount--) {
const mailMessageFolder = mailMessageFolders[mailMessageFoldersCount];
if (mailMessageFolder && mailMessageFolder.id === 1) {
return mailMessageFolder.unread_messages;
}
}
return 0;
}
function sendUnreadUsers(unreadUsers) {
if (!unreadUsers) {
return;
}
for (let tenant in unreadUsers) {
if (!unreadUsers.hasOwnProperty(tenant)) continue;
for (let user in unreadUsers[tenant]) {
if (!unreadUsers[tenant].hasOwnProperty(user)) continue;
counters.to(`${tenant}-${user}`).emit('sendFeedsCount', unreadUsers[tenant][user]);
}
}
}
function sendUnreadCounts({ tenantId, unreadCounts } = {}) {
if (typeof tenantId === "undefined") {
return;
}
for (let user in unreadCounts) {
if (unreadCounts.hasOwnProperty(user)) {
counters.to(`${tenantId}-${user}`).emit('sendMessagesCount', unreadCounts[user]);
}
}
}
function updateFolders({ tenant, userId, count } = {}) {
if (typeof tenant === "undefined" || !userId || !count) {
return;
}
counters.to(`${tenant}-${userId}`).emit('updateFolders', count);
}
function sendMailNotification({ tenant, userId, state } = {}) {
if (typeof tenant === "undefined" || !userId || typeof state === "undefined") {
return;
}
counters.to(`${tenant}-${userId}`).emit('sendMailNotification', state);
}
return { sendUnreadUsers, sendUnreadCounts, updateFolders, sendMailNotification };
}

View File

@ -1,51 +1,118 @@
module.exports = (io) => {
const log = require("../log.js");
const files = io.of("/files");
const logger = require("../log.js");
const moment = require("moment");
const filesIO = io; //TODO: Restore .of("/files");
files.on("connection", (socket) => {
const request = socket.client.request;
if (!request.user || !request.user.id) {
return;
}
filesIO.on("connection", (socket) => {
const session = socket.handshake.session;
const tenantId = request.portal.tenantId;
socket
.on("subscribeChangeEditors", (fileIds) => {
if (typeof fileIds != "object") {
fileIds = [fileIds];
}
fileIds.forEach(function(fileId) {
let room = `${tenantId}-${fileId}`;
socket.join(room);
});
});
});
function changeEditors({ tenantId, fileId, finish } = {}) {
if (typeof tenantId === "undefined" || typeof fileId === "undefined") {
log.error(`files: changeEditors without arguments`);
return;
}
let room = `${tenantId}-${fileId}`;
files.to(room).emit("changeEditors", fileId);
if (finish) {
files.in(room).clients((error, clients) => {
if (error) throw error;
clients.forEach(function(client) {
let clientSocket = files.connected[client];
if(clientSocket){
clientSocket.leave(room);
}
});
});
}
if (!session) {
logger.error("empty session");
return;
}
return { changeEditors };
};
if (session.system) {
logger.info(`connect system as socketId='${socket.id}'`);
socket.on("ping", (date) => {
logger.info(`ping (client ${socket.id}) at ${date}`);
filesIO.to(socket.id).emit("pong", moment.utc());
});
socket.on("disconnect", (reason) => {
logger.info(
`disconnect system as socketId='${socket.id}' due to ${reason}`
);
});
return;
}
if (!session.user) {
logger.error("invalid session: unknown user");
return;
}
if (!session.portal) {
logger.error("invalid session: unknown portal");
return;
}
const userId = session?.user?.id;
const tenantId = session?.portal?.tenantId;
getRoom = (roomPart) => {
return `${tenantId}-${roomPart}`;
};
logger.info(
`connect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}'`
);
socket.on("disconnect", (reason) => {
logger.info(
`disconnect user='${userId}' on tenant='${tenantId}' socketId='${socket.id}' due to ${reason}`
);
});
socket.on("subscribe", (roomParts) => {
if (!roomParts) return;
if (Array.isArray(roomParts)) {
const rooms = roomParts.map((p) => getRoom(p));
logger.info(`client ${socket.id} join rooms [${rooms.join(",")}]`);
socket.join(rooms);
} else {
const room = getRoom(roomParts);
logger.info(`client ${socket.id} join room ${room}`);
socket.join(room);
}
});
socket.on("unsubscribe", (roomParts) => {
if (!roomParts) return;
if (Array.isArray(roomParts)) {
const rooms = roomParts.map((p) => getRoom(p));
logger.info(`client ${socket.id} leave rooms [${rooms.join(",")}]`);
socket.leave(rooms);
} else {
const room = getRoom(roomParts);
logger.info(`client ${socket.id} leave room ${room}`);
socket.leave(room);
}
});
socket.on("refresh-folder", (folderId) => {
const room = getRoom(`DIR-${folderId}`);
logger.info(`refresh folder ${folderId} in room ${room}`);
socket.to(room).emit("refresh-folder", folderId);
});
});
function startEdit({ fileId, room } = {}) {
logger.info(`start edit file ${fileId} in room ${room}`);
filesIO.to(room).emit("s:start-edit-file", fileId);
}
function stopEdit({ fileId, room } = {}) {
logger.info(`stop edit file ${fileId} in room ${room}`);
filesIO.to(room).emit("s:stop-edit-file", fileId);
}
function modifyFolder(room, cmd, id, type, data) {
filesIO.to(room).emit("s:modify-folder", { cmd, id, type, data });
}
function createFile({ fileId, room, data } = {}) {
logger.info(`create new file ${fileId} in room ${room}`);
modifyFolder(room, "create", fileId, "file", data);
}
function deleteFile({ fileId, room } = {}) {
logger.info(`delete file ${fileId} in room ${room}`);
modifyFolder(room, "delete", fileId, "file");
}
return { startEdit, stopEdit, createFile, deleteFile };
};

View File

@ -1,119 +0,0 @@
module.exports = (io) => {
const phones = new (require("./voipListPhones.js"))();
const numberIdKey = "numberId";
const voip = io.of("/voip");
voip.on('connection', (socket) => {
const request = socket.client.request;
if(!request.user || !request.user.id) return;
const user = request.user;
const userId = user.id;
const numberId = getNumberId(request);
const numberRoom = request.portal.tenantId + numberId;
socket.join([numberRoom, userId]);
phones.addPhone(numberId);
socket
.on("status", status)
.on("miss", miss.bind(null, numberId))
.on("enqueue", enqueue.bind(null, numberId))
.on("Dequeue", dequeue)
.on("OnlineAgents", onlineAgents)
.on("getStatus", getStatus)
.on("getAgent", getAgent.bind(null, numberId))
.on("incoming", incoming)
.on("start", start)
.on("end", end);
function getNumberId(request) {
let result = request._query[numberIdKey];
if (!result) {
result = request.headers[numberIdKey];
}
return result || "";
}
function status(agentStatus, fn) {
voip.to(userId).emit("status", agentStatus);
switch (agentStatus) {
case 0:
socket.once('disconnect', () => {
status(2);
});
if (phones.anyCalls(numberId)) {
dequeue();
} else {
phones.addOrUpdateAgent(numberId, { id: userId, status: agentStatus });
}
break;
case 1:
phones.addOrUpdateAgent(numberId, { id: userId, status: agentStatus });
break;
case 2:
phones.removeAgent(numberId, userId);
break;
}
onlineAgents();
if(fn) fn();
}
function dequeue() {
voip.to(userId).emit('dequeue', phones.dequeueCall(numberId));
}
function onlineAgents() {
voip.to(numberRoom).emit('onlineAgents', phones.onlineAgents(numberId));
}
function getStatus(fn) {
const getStatusResult = phones.getStatus(numberId, userId);
fn(getStatusResult);
}
function start() {
voip.to(userId).emit('start');
}
function end() {
voip.to(userId).emit('end');
}
});
function enqueue(numberId, callId, agent) {
const result = phones.enqueue(numberId, callId, agent);
if (result) {
voip.to(result).emit('dequeue', callId);
}
}
function incoming(callId, agent) {
voip.to(agent).emit('dequeue', callId);
}
function miss(numberId, callId, agent) {
phones.removeCall(numberId, callId);
voip.to(agent).emit('miss', callId);
}
function getAgent(numberId, contactsResponsibles, fn) {
const result = phones.getAgent(numberId, contactsResponsibles);
if (typeof fn === "function") fn(result);
return result;
}
function reload(numberRoom, agent) {
if (agent) {
voip.to(agent).emit('reload');
}else{
voip.to(numberRoom).emit('reload');
}
}
return { enqueue, incoming, miss, getAgent, reload };
}

View File

@ -1,74 +0,0 @@
const VoipPhone = require('./voipPhone.js');
class ListVoipPhone {
constructor() {
this.phones = [];
}
addPhone(numberId) {
const existingPhone = this.phones.find((item) => item.numberId === numberId);
if (!existingPhone) {
this.phones.push(new VoipPhone(numberId));
}
}
getPhone(numberId) {
return this.phones.find((item) => item.numberId === numberId);
}
addOrUpdateAgent(numberId, agent) {
const phone = this.getPhone(numberId);
if (!phone) return;
phone.addOrUpdateAgent(agent);
}
removeAgent(numberId, agentId) {
const phone = this.getPhone(numberId);
if (!phone) return;
phone.removeAgent(agentId);
}
anyCalls(numberId) {
const phone = this.getPhone(numberId);
if (!phone) return false;
return phone.anyCalls();
}
dequeueCall(numberId) {
const phone = this.getPhone(numberId);
if (!phone) return "";
return phone.dequeueCall();
}
removeCall(numberId, callId) {
const phone = this.getPhone(numberId);
if (!phone) return;
phone.removeCall(callId);
}
enqueue(numberId, callId, agent) {
const phone = this.getPhone(numberId);
if (!phone) return "";
return phone.enqueue(callId, agent);
}
onlineAgents(numberId) {
const phone = this.getPhone(numberId);
if (!phone) return [];
return phone.onlineAgents();
}
getStatus(numberId, agentId) {
const phone = this.getPhone(numberId);
if (!phone) return 2;
return phone.getAgentStatus(agentId);
}
getAgent(numberId, contactResponsibles) {
const phone = this.getPhone(numberId);
if (!phone) return null;
return phone.getAgent(contactResponsibles);
}
}
module.exports = ListVoipPhone;

View File

@ -1,87 +0,0 @@
function findAgent(agentId, item) {
return item.id === agentId;
}
class VoipPhone {
constructor(numberId) {
this.agents = [];
this.calls = [];
this.numberId = numberId;
}
addOrUpdateAgent(agent) {
const existingAgent = this.agents.find(findAgent.bind(null, agent.Id));
if (existingAgent) {
existingAgent.status = agent.status;
} else {
this.agents.push(agent);
}
}
removeAgent(agentId) {
const existingAgent = this.agents.some(findAgent.bind(null, agentId));
if (!existingAgent) return false;
this.agents = this.agents.filter((item) => item.id !== agentId);
return true;
}
addCall(callId) {
if (this.calls.some((item) => item === callId)) return;
this.calls.push(callId);
}
removeCall(callId) {
if (!callId || !this.calls.some((item) => item === callId)) return;
this.calls = this.calls.filter((item) => item !== callId);
}
anyCalls() {
return !!this.calls.length;
}
dequeueCall() {
return this.calls.pop();
}
enqueue(callId, agent) {
if (agent && this.removeAgent(agent)) {
return agent;
}
const agents = this.agents.filter((item) =>item.status === 0 );
if (agents.length) {
return agents[0].id;
}
this.addCall(callId);
return "";
}
onlineAgents() {
return this.agents.filter((item) => item.status === 0).map((item) => item.id);
}
getAgentStatus(agentId) {
const agent = this.agents.find(findAgent.bind(null, agentId));
return agent ? agent.status : 2;
}
getAgent(contactResponsibles) {
const isAnyNotOffline = !!this.agents.length;
let result = this.agents.find((item) => {
return item.status === 0 && contactResponsibles.includes(item.id);
});
if (!result) {
result = this.agents.find((item) => item.status === 0);
}
if (result) {
result.status = 1;
}
return { result, isAnyNotOffline };
}
}
module.exports = VoipPhone;

View File

@ -1,32 +1,54 @@
const winston = require('winston');
require('winston-daily-rotate-file')
const winston = require("winston");
require("winston-daily-rotate-file");
const path = require('path');
const config = require('../config');
const fs = require('fs');
const fileName = config.get("logPath") || path.join(__dirname, "..", "..", "Logs", "web.socketio.%DATE%.log");
const path = require("path");
const config = require("../config");
const fs = require("fs");
const fileName =
config.get("logPath") ||
path.join(__dirname, "..", "..", "..", "Logs", "socket-io.%DATE%.log");
const dirName = path.dirname(fileName);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
fs.mkdirSync(dirName);
}
const fileTransport = new (winston.transports.DailyRotateFile)(
{
var options = {
file: {
filename: fileName,
datePattern: 'MM-DD',
datePattern: "MM-DD",
handleExceptions: true,
humanReadableUnhandledException: true,
zippedArchive: true,
maxSize: '50m',
maxFiles: '30d'
});
maxSize: "50m",
maxFiles: "30d",
json: true,
},
console: {
level: "debug",
handleExceptions: true,
json: false,
colorize: true,
},
};
//const fileTransport = new winston.transports.DailyRotateFile(options.file);
const transports = [
new (winston.transports.Console)(),
fileTransport
new winston.transports.Console(options.console),
new winston.transports.DailyRotateFile(options.file),
];
winston.handleExceptions(fileTransport);
//winston.exceptions.handle(fileTransport);
module.exports = new winston.Logger({ transports: transports, exitOnError: false});
module.exports = new winston.createLogger({
//defaultMeta: { component: "socket.io-server" },
format: winston.format.combine(
winston.format.timestamp({
format: "YYYY-MM-DD HH:mm:ss",
}),
winston.format.json()
),
transports: transports,
exitOnError: false,
});

View File

@ -1,49 +1,66 @@
module.exports = function (socket, next) {
const apiRequestManager = require('../apiRequestManager.js');
const req = socket.client.request;
const authService = require('./authService.js')();
const co = require('co');
const session = socket.handshake.session;
const request = require("../requestManager.js");
const check = require("./authService.js");
const portalManager = require("../portalManager.js");
if (req.user) {
next();
return;
module.exports = (socket, next) => {
const req = socket.client.request;
const session = socket.handshake.session;
const cookie = req?.cookies?.authorization || req?.cookies?.asc_auth_key;
const token = req?.headers?.authorization;
if (!cookie && !token) {
socket.disconnect("unauthorized");
next(new Error("Authentication error"));
return;
}
if (token) {
if (!check(token)) {
next(new Error("Authentication error"));
} else {
session.system = true;
session.save();
next();
}
return;
}
if (!req.cookies || (!req.cookies['asc_auth_key'] && !req.cookies['authorization'])) {
socket.disconnect('unauthorized');
next(new Error('Authentication error'));
return;
}
let headers;
if (cookie)
headers = {
Authorization: cookie,
};
if(session && session.user && session.portal && typeof(session.mailEnabled) !== "undefined") {
req.user = session.user;
req.portal = session.portal;
req.mailEnabled = session.mailEnabled;
next();
return;
}
const basePath = portalManager(req).replace(/\/$/g, "");
if(req.cookies['authorization']){
if(!authService(req)){
next(new Error('Authentication error'));
} else{
next();
}
return;
}
co(function*(){
var batchRequest = apiRequestManager.batchFactory()
.get("people/@self.json?fields=id,userName,displayName")
.get("portal.json?fields=tenantId,tenantDomain")
.get("settings/security/2A923037-8B2D-487b-9A22-5AC0918ACF3F");
[session.user, session.portal, session.mailEnabled] = [req.user, req.portal, req.mailEnabled] = yield apiRequestManager.batch(batchRequest, req);
session.save();
next();
}).catch((err) => {
socket.disconnect('unauthorized');
next(new Error('Authentication error'));
const getUser = () => {
return request({
method: "get",
url: "/people/@self.json?fields=id,userName,displayName",
headers,
basePath,
});
}
};
const getPortal = () => {
return request({
method: "get",
url: "/portal.json?fields=tenantId,tenantDomain",
headers,
basePath,
});
};
return Promise.all([getUser(), getPortal()])
.then(([user, portal]) => {
session.user = user;
session.portal = portal;
session.save();
next();
})
.catch((err) => {
socket.disconnect("Unauthorized");
next(new Error("Authentication error"));
});
};

View File

@ -1,37 +1,35 @@
module.exports = () => {
const
config = require('../../config'),
crypto = require('crypto'),
moment = require('moment');
const config = require("../../config"),
crypto = require("crypto"),
moment = require("moment");
const skey = config.get("core.machinekey");
const trustInterval = 5 * 60 * 1000;
const skey = config.get("core.machinekey");
const trustInterval = 5 * 60 * 1000;
function check(req) {
const authHeader = req.headers["authorization"] || req.cookies["authorization"];
if(!authHeader) return false;
function check(token) {
if (!token || typeof token !== "string") return false;
const splitted = authHeader.split(':');
if (splitted.length < 3) return false;
const splitted = token.split(":");
if (splitted.length < 3) return false;
const pkey = splitted[0].substr(4);
const date = splitted[1];
const orighash = splitted[2];
const pkey = splitted[0].substr(4);
const date = splitted[1];
const orighash = splitted[2];
const timestamp = moment.utc(date, "YYYYMMDDHHmmss");
if (moment.utc() - timestamp > trustInterval) {
return false;
}
const hasher = crypto.createHmac('sha1', skey);
const hash = hasher.update(date + "\n" + pkey);
if (hash.digest('base64') !== orighash) {
return false;
}
return true;
const timestamp = moment.utc(date, "YYYYMMDDHHmmss");
if (moment.utc() - timestamp > trustInterval) {
return false;
}
return check;
};
const hasher = crypto.createHmac("sha1", skey);
const hash = hasher.update(date + "\n" + pkey);
if (hash.digest("base64") !== orighash) {
return false;
}
return true;
}
return check;
};

View File

@ -1,16 +1,20 @@
const portalInternalUrl = require('../config').get("portal.internal.url")
const portalInternalUrl = require("../config").get("portal.internal.url");
module.exports = (req) => {
if(portalInternalUrl) return portalInternalUrl;
if (portalInternalUrl) return portalInternalUrl;
const xRewriterUrlInternalHeader = 'x-rewriter-url-internal';
if (req.headers && req.headers[xRewriterUrlInternalHeader]) {
return req.headers[xRewriterUrlInternalHeader];
}
const xRewriterUrlInternalHeader = "x-rewriter-url-internal";
if (req.headers && req.headers[xRewriterUrlInternalHeader]) {
return req.headers[xRewriterUrlInternalHeader];
}
const xRewriterUrlHeader = 'x-rewriter-url';
if (req.headers && req.headers[xRewriterUrlHeader]) {
return req.headers[xRewriterUrlHeader];
}
const xRewriterUrlHeader = "x-rewriter-url";
if (req.headers && req.headers[xRewriterUrlHeader]) {
return req.headers[xRewriterUrlHeader];
}
return "";
};
if (req?.headers?.origin) {
return req.headers.origin;
}
return "";
};

View File

@ -0,0 +1,47 @@
const axios = require("axios");
const apiPrefixURL = "/api/2.0";
const apiTimeout = 30000;
module.exports = (options) => {
const basePath = options.basePath;
const url = `${basePath}${apiPrefixURL}${options.url}`;
const axiosOptions = {
baseURL: url,
responseType: "json",
timeout: apiTimeout,
headers: options.headers,
};
const getResponseError = (res) => {
if (!res) return;
if (res.data && res.data.error) {
return res.data.error.message;
}
if (res.isAxiosError && res.message) {
return res.message;
}
};
const onSuccess = (response) => {
const error = getResponseError(response);
if (error) throw new Error(error);
if (!response || !response.data || response.isAxiosError) return null;
if (response.request.responseType === "text") return response.data;
return response.data.response;
};
const onError = (error) => {
const errorText = error.response
? getResponseError(error.response)
: error.message;
return Promise.reject(errorText || error);
};
const request = axios.create(axiosOptions);
return request().then(onSuccess).catch(onError);
};

View File

@ -2,11 +2,12 @@
"port": 9899,
"core.machinekey": "1123askdasjklasbnd",
"portal.internal.url": "",
"redis":{
"redis": {
"enabled": false,
"host": "localhost",
"port": 6379,
"db": 0,
"pass":"",
"ttl":84600
}
}
"db": 0,
"pass": "",
"ttl": 84600
}
}

View File

@ -1,32 +1,25 @@
{
"name": "asc.socket.io",
"version": "0.0.0",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"description": "ASC.Socket.IO",
"author": {
"name": "Pavel"
"start": "node server.js",
"start:dev": "nodemon server.js"
},
"dependencies": {
"connect-redis": "~3.4.0",
"body-parser": "~1.18.2",
"co": "^4.6.0",
"cookie-parser": "~1.4.3",
"debug": "~3.1.0",
"express": "~4.16.4",
"express-session": "~1.15.6",
"connect-redis": "~6.0.0",
"cookie-parser": "~1.4.6",
"express": "~4.17.2",
"express-session": "~1.17.2",
"express-socket.io-session": "~1.3.5",
"memorystore": "^1.6.0",
"moment": "^2.24.0",
"morgan": "~1.9.1",
"nconf": "^0.10.0",
"redis": "^3.1.1",
"request": "^2.88.0",
"socket.io": "^2.2.0",
"ua-parser-js": "^0.7.19",
"winston": "^2.4.1",
"winston-daily-rotate-file": "^3.2.0"
"memorystore": "^1.6.6",
"moment": "^2.29.1",
"morgan": "^1.10.0",
"nconf": "^0.11.3",
"nodemon": "^2.0.15",
"redis": "^3.1.2",
"socket.io": "^4.4.0",
"winston": "^3.3.3",
"winston-daily-rotate-file": "^4.5.5"
}
}

View File

@ -0,0 +1,99 @@
const express = require("express");
const { Server } = require("socket.io");
const { createServer } = require("http");
const logger = require("morgan");
const redis = require("redis");
const expressSession = require("express-session");
const cookieParser = require("cookie-parser");
const RedisStore = require("connect-redis")(expressSession);
const MemoryStore = require("memorystore")(expressSession);
const sharedsession = require("express-socket.io-session");
const config = require("./config");
const auth = require("./app/middleware/auth.js");
const winston = require("./app/log.js");
winston.stream = {
write: (message) => winston.info(message),
};
const port = config.get("port") || 9899;
const app = express();
const secret = config.get("core.machinekey") + new Date().getTime();
const secretCookieParser = cookieParser(secret);
const baseCookieParser = cookieParser();
const redisOptions = config.get("redis");
let store;
if (redisOptions?.enabled) {
const redisClient = redis.createClient(redisOptions);
store = new RedisStore({ client: redisClient });
} else {
store = new MemoryStore();
}
const session = expressSession({
store: store,
secret: secret,
resave: true,
saveUninitialized: true,
cookie: {
path: "/",
httpOnly: true,
secure: false,
maxAge: null,
},
cookieParser: secretCookieParser,
name: "socketio.sid",
});
app.use(logger("dev", { stream: winston.stream }));
app.use(session);
const httpServer = createServer(app);
const options = {
cors: {
//origin: "http://localhost:8092",
methods: ["GET", "POST"],
allowedHeaders: ["Authorization"],
//credentials: true,
},
allowRequest: (req, cb) => {
baseCookieParser(req, null, () => {});
const token =
req?.headers?.authorization ||
req?.cookies?.authorization ||
req?.cookies?.asc_auth_key;
if (!token) {
winston.info(`not allowed request: empty token`);
return cb("auth", false);
}
return cb("auth", true);
},
};
const io = new Server(httpServer, options);
io.use(sharedsession(session, secretCookieParser, { autoSave: true }))
.use((socket, next) => {
baseCookieParser(socket.client.request, null, next);
})
.use((socket, next) => {
auth(socket, next);
});
app.get("/", (req, res) => {
res.send("<h1>Invalid Endpoint</h1>");
});
const filesHub = require("./app/hubs/files.js")(io);
app.use("/controller", require("./app/controllers")(filesHub));
httpServer.listen(port, () => winston.info(`Server started on port: ${port}`));
module.exports = io;

View File

@ -1,15 +0,0 @@
{
"globalDependencies": {
"body-parser": "registry:dt/body-parser#0.0.0+20160317120654",
"cookie-parser": "registry:dt/cookie-parser#1.3.4+20160316155526",
"debug": "registry:dt/debug#0.0.0+20160317120654",
"express": "registry:dt/express#4.0.0+20160317120654",
"express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160602151406",
"jade": "registry:dt/jade#0.0.0+20160316155526",
"mime": "registry:dt/mime#0.0.0+20160316155526",
"morgan": "registry:dt/morgan#1.7.0+20160524142355",
"serve-favicon": "registry:dt/serve-favicon#0.0.0+20160316155526",
"serve-static": "registry:dt/serve-static#0.0.0+20160606155157",
"stylus": "registry:dt/stylus#0.0.0+20160317120654"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,7 @@
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="7.1.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="5.0.1" />
<PackageReference Include="WebSocketSharpNetStandart" Version="1.0.3-rc12" />
<PackageReference Include="SocketIOClient" Version="3.0.5" />
</ItemGroup>
<ItemGroup>

View File

@ -25,6 +25,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
@ -40,20 +41,21 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using WebSocketSharp;
using SocketIOClient;
namespace ASC.Socket.IO.Svc
{
[Singletone]
public class SocketServiceLauncher : IHostedService
{
private const int PingInterval = 10000;
private const int PingInterval = 10000;
private const int ReconnectAttempts = 5;
private Process Proc { get; set; }
private ProcessStartInfo StartInfo { get; set; }
private WebSocket WebSocket { get; set; }
private SocketIO SocketClient { get; set; }
private CancellationTokenSource CancellationTokenSource { get; set; }
private ILog Logger { get; set; }
private ILog Logger { get; set; }
private string LogDir { get; set; }
private IConfiguration Configuration { get; set; }
private ConfigurationExtension ConfigurationExtension { get; }
@ -70,7 +72,6 @@ namespace ASC.Socket.IO.Svc
IHostEnvironment hostEnvironment)
{
Logger = options.CurrentValue;
CancellationTokenSource = new CancellationTokenSource();
Configuration = configuration;
ConfigurationExtension = configurationExtension;
CoreBaseSettings = coreBaseSettings;
@ -90,9 +91,10 @@ namespace ASC.Socket.IO.Svc
UseShellExecute = false,
FileName = "node",
WindowStyle = ProcessWindowStyle.Hidden,
Arguments = string.Format("\"{0}\"", Path.GetFullPath(CrossPlatform.PathCombine(HostEnvironment.ContentRootPath, settings.Path, "app.js"))),
Arguments = string.Format("\"{0}\"", Path.GetFullPath(CrossPlatform.PathCombine(HostEnvironment.ContentRootPath, settings.Path, "server.js"))),
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
};
};
StartInfo.EnvironmentVariables.Add("core.machinekey", Configuration["core:machinekey"]);
StartInfo.EnvironmentVariables.Add("port", settings.Port);
@ -106,15 +108,16 @@ namespace ASC.Socket.IO.Svc
{
StartInfo.EnvironmentVariables.Add("portal.internal.url", "http://localhost");
}
LogDir = Logger.LogDirectory;
StartInfo.EnvironmentVariables.Add("logPath", CrossPlatform.PathCombine(LogDir, "web.socketio.log"));
LogDir = Logger.LogDirectory;
StartInfo.EnvironmentVariables.Add("logPath", CrossPlatform.PathCombine(LogDir, "socket-io.%DATE%.log"));
StartNode();
}
catch (Exception e)
{
Logger.Error(e);
}
}
return Task.CompletedTask;
}
@ -128,9 +131,11 @@ namespace ASC.Socket.IO.Svc
private void StartNode()
{
StopNode();
Proc = Process.Start(StartInfo);
var task = new Task(StartPing, CancellationTokenSource.Token, TaskCreationOptions.LongRunning);
Proc = Process.Start(StartInfo);
CancellationTokenSource = new CancellationTokenSource();
var task = new Task(StartPing, CancellationTokenSource.Token, TaskCreationOptions.LongRunning);
task.Start(TaskScheduler.Default);
}
@ -156,93 +161,118 @@ namespace ASC.Socket.IO.Svc
}
}
private void StartPing()
private async void StartPing()
{
Thread.Sleep(PingInterval);
var error = false;
WebSocket = new WebSocket(string.Format("ws://127.0.0.1:{0}/socket.io/?EIO=3&transport=websocket", StartInfo.EnvironmentVariables["port"]));
WebSocket.SetCookie(new WebSocketSharp.Net.Cookie("authorization", SignalrServiceClient.CreateAuthToken()));
WebSocket.EmitOnPing = true;
WebSocket.Log.Level = WebSocketSharp.LogLevel.Trace;
WebSocket.Log.Output = (logData, filePath) =>
{
if (logData.Message.Contains("SocketException"))
{
error = true;
}
Logger.Debug(logData.Message);
};
WebSocket.OnOpen += (sender, e) =>
{
Logger.Info("Open");
error = false;
Thread.Sleep(PingInterval);
Task.Run(() =>
{
while (WebSocket.Ping())
{
Logger.Debug("Ping " + WebSocket.ReadyState);
Thread.Sleep(PingInterval);
}
Logger.Debug("Reconnect" + WebSocket.ReadyState);
}, CancellationTokenSource.Token);
};
WebSocket.OnClose += (sender, e) =>
{
Logger.Info("Close");
if (CancellationTokenSource.IsCancellationRequested) return;
if (error)
{
Process.GetCurrentProcess().Kill();
}
else
{
WebSocket.Connect();
}
};
WebSocket.OnMessage += (sender, e) =>
{
if (e.Data.Contains("error"))
{
Logger.Error("Auth error");
CancellationTokenSource.Cancel();
}
};
WebSocket.OnError += (sender, e) =>
{
Logger.Error("Error", e.Exception);
};
WebSocket.Connect();
}
try
{
var settings = ConfigurationExtension.GetSetting<SocketSettings>("socket");
var uri = new Uri($"ws://localhost:{settings.Port}"); //TODO: replace localhost to variable
var token = SignalrServiceClient.CreateAuthToken();
SocketClient = new SocketIO(uri, new SocketIOOptions
{
ExtraHeaders = new Dictionary<string, string>
{
{ "Authorization", token }
},
ConnectionTimeout = TimeSpan.FromSeconds(30),
Reconnection = true,
ReconnectionAttempts = ReconnectAttempts,
EIO = 4,
Path = "/socket.io",
Transport = SocketIOClient.Transport.TransportProtocol.WebSocket,
RandomizationFactor = 0.5
});
SocketClient.OnConnected += IOClient_OnConnected;
SocketClient.OnDisconnected += IOClient_OnDisconnected;
SocketClient.OnReconnectAttempt += IOClient_OnReconnectAttempt;
SocketClient.OnError += IOClient_OnError;
SocketClient.On("pong", response =>
{
Logger.Debug($"pong (server) at {response}");
});
Logger.Debug("Try to connect...");
await SocketClient.ConnectAsync();
}
catch (Exception ex)
{
if (CancellationTokenSource.IsCancellationRequested) return;
Logger.Error(ex.Message);
StopNode();
Process.GetCurrentProcess().Kill();
}
}
private async void IOClient_OnConnected(object sender, EventArgs e)
{
var socket = sender as SocketIO;
Logger.Info($"Socket_OnConnected Socket.Id: {socket.Id}");
while (SocketClient.Connected)
{
if (CancellationTokenSource.IsCancellationRequested) return;
await Task.Delay(PingInterval);
if (!SocketClient.Connected)
break;
await SocketClient.EmitAsync("ping", DateTime.UtcNow.ToString());
}
}
private void IOClient_OnDisconnected(object sender, string e)
{
Logger.Debug($"Socket_OnDisconnected {e}");
}
private void IOClient_OnReconnectAttempt(object sender, int attempt)
{
Logger.Debug($"Try to reconnect... attempt {attempt}");
if (attempt >= ReconnectAttempts)
{
StopPing();
Process.GetCurrentProcess().Kill();
}
}
private void IOClient_OnError(object sender, string e)
{
Logger.Error($"IOClient_OnError {e}");
}
private void StopPing()
{
try
{
if (SocketClient != null)
{
SocketClient.OnConnected -= IOClient_OnConnected;
SocketClient.OnDisconnected -= IOClient_OnDisconnected;
SocketClient.OnReconnectAttempt -= IOClient_OnReconnectAttempt;
SocketClient.OnError -= IOClient_OnError;
SocketClient.Dispose();
SocketClient = null;
}
CancellationTokenSource.Cancel();
if (WebSocket.IsAlive)
{
WebSocket.Close();
WebSocket = null;
}
}
catch (Exception)
catch (Exception ex)
{
Logger.Error("Ping failed stop");
Logger.Error($"Ping failed stop {ex.Message}");
StopNode();
Process.GetCurrentProcess().Kill();
}
}
}

View File

@ -95,8 +95,8 @@
"images": "images",
"hide-settings": "Monitoring,LdapSettings,DocService,MailService,PublicPortal,ProxyHttpContent,SpamSubscription,FullTextSearch",
"hub": {
"url": "",
"internal": ""
"url": "/socket.io",
"internal": "http://localhost:9899/"
},
"cultures": "de,en,fr,it,pt-BR,ru",
"url-shortener": {

View File

@ -31,6 +31,10 @@ map $request_uri $cache_control {
include /etc/nginx/includes/onlyoffice-*.conf;
upstream upstream-nodejs {
server 127.0.0.1:9899;
}
server {
listen 8092;
@ -123,7 +127,7 @@ server {
#rewrite login/(.*) /$1 break;
proxy_pass http://localhost:5011;
}
location /sockjs-node {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
@ -201,6 +205,20 @@ server {
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
}
location /socket.io/ {
proxy_pass http://upstream-nodejs;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
}
location /login.ashx {
proxy_pass http://localhost:5003;
proxy_set_header X-REWRITER-URL $X_REWRITER_URL;
@ -246,7 +264,7 @@ server {
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location ~* /files {
#rewrite products/files/(.*) /$1 break;
proxy_pass http://localhost:5008;

View File

@ -213,6 +213,10 @@ class FilesFilter {
return str;
};
getLastPage() {
return Math.ceil(this.total / this.pageCount) - 1;
}
clone() {
return new FilesFilter(
this.page,

View File

@ -308,6 +308,9 @@ class MediaViewer extends React.Component {
this.setState({
playlistPos: currentPlaylistPos,
});
const id = playlist[currentPlaylistPos].fileId;
this.props.onChangeUrl(id);
};
nextMedia = () => {
@ -319,6 +322,9 @@ class MediaViewer extends React.Component {
this.setState({
playlistPos: currentPlaylistPos,
});
const id = playlist[currentPlaylistPos].fileId;
this.props.onChangeUrl(id);
};
getOffset = () => {
@ -419,8 +425,8 @@ class MediaViewer extends React.Component {
}
};
onClose = () => {
this.props.onClose();
onClose = (e) => {
this.props.onClose(e);
this.setState({ visible: false });
};
@ -450,7 +456,7 @@ class MediaViewer extends React.Component {
canDelete,
canDownload,
errorLabel,
previewFile,
isPreviewFile,
} = this.props;
const currentFileId =
@ -547,7 +553,7 @@ class MediaViewer extends React.Component {
<div className="mediaViewerToolbox" ref={this.viewerToolbox}>
{!isImage && (
<span>
{canDelete(currentFileId) && !previewFile && (
{canDelete(currentFileId) && !isPreviewFile && (
<ControlBtn onClick={this.onDelete}>
<div className="deleteBtnContainer">
<StyledMediaDeleteIcon size="scale" />
@ -585,7 +591,8 @@ MediaViewer.propTypes = {
onEmptyPlaylistError: PropTypes.func,
deleteDialogVisible: PropTypes.bool,
errorLabel: PropTypes.string,
previewFile: PropTypes.bool,
isPreviewFile: PropTypes.bool,
onChangeUrl: PropTypes.func,
};
MediaViewer.defaultProps = {
@ -598,7 +605,7 @@ MediaViewer.defaultProps = {
canDownload: () => {
return true;
},
previewFile: false,
isPreviewFile: false,
};
export default MediaViewer;

View File

@ -39,6 +39,7 @@
"react-window-infinite-loader": "^1.0.7",
"screenfull": "^5.1.0",
"sjcl": "^1.0.8",
"socket.io-client": "^4.4.0",
"styled-components": "^5.3.1",
"workbox-window": "^6.3.0"
},

View File

@ -5,6 +5,7 @@ import { combineUrl } from "../utils";
import FirebaseHelper from "../utils/firebase";
import { AppServerConfig } from "../constants";
import { version } from "../package.json";
import SocketIOHelper from "../utils/socket";
const { proxyURL } = AppServerConfig;
class SettingsStore {
@ -93,6 +94,7 @@ class SettingsStore {
documentServer: "6.4.1",
};
debugInfo = false;
socketUrl = "";
userFormValidation = /^[\p{L}\p{M}'\-]+$/gu;
folderFormValidation = new RegExp('[*+:"<>?|\\\\/]', "gim");
@ -349,6 +351,10 @@ class SettingsStore {
return window.firebaseHelper;
}
get socketHelper() {
return new SocketIOHelper(this.socketUrl);
}
getBuildVersionInfo = async () => {
const versionInfo = await api.settings.getBuildVersion();
this.setBuildVersionInfo(versionInfo);

View File

@ -0,0 +1,59 @@
import io from "socket.io-client";
let client = null;
class SocketIOHelper {
socketUrl = null;
constructor(url) {
if (!url) return;
this.socketUrl = url;
if (client) return;
const origin = window.location.origin;
client = io(origin, {
withCredentials: true,
transports: ["websocket", "polling"],
eio: 4,
path: url,
});
client.on("connect", () => console.log("socket is connected"));
client.on("connect_error", (err) =>
console.log("socket connect error", err)
);
client.on("disconnect", () => console.log("socket is disconnected"));
}
get isEnabled() {
return this.socketUrl !== null;
}
emit = ({ command, data, room = null }) => {
if (!this.isEnabled) return;
if (!client.connected) {
client.on("connect", () => {
room ? client.to(room).emit(command, data) : client.emit(command, data);
});
} else {
room ? client.to(room).emit(command, data) : client.emit(command, data);
}
};
on = (eventName, callback) => {
if (!this.isEnabled) return;
if (!client.connected) {
client.on("connect", () => {
client.on(eventName, callback);
});
} else {
client.on(eventName, callback);
}
};
}
export default SocketIOHelper;

View File

@ -204,7 +204,11 @@ export default function withFileActions(WrappedFileItem) {
}
if (isMediaOrImage) {
localStorage.setItem("isFirstUrl", window.location.href);
setMediaViewerData({ visible: true, id });
const url = "/products/files/#preview/" + id;
history.pushState(null, null, url);
return;
}

View File

@ -34,10 +34,10 @@ const Badges = ({
versionGroup,
title,
fileExst,
isEditing,
} = item;
const isFavorite = fileStatus === 32;
const isEditing = fileStatus === 1;
const isNewWithFav = fileStatus === 34;
const isEditingWithFav = fileStatus === 33;
const showEditBadge = !locked || item.access === 0;

View File

@ -41,6 +41,7 @@ const ConflictResolveDialog = (props) => {
activeFiles,
setActiveFiles,
setMoveToPanelVisible,
setCopyPanelVisible,
setThirdPartyMoveDialogVisible,
} = props;
@ -61,6 +62,7 @@ const ConflictResolveDialog = (props) => {
const onClosePanels = () => {
setConflictResolveDialogVisible(false);
setMoveToPanelVisible(false);
setCopyPanelVisible(false);
setThirdPartyMoveDialogVisible(false);
};
const onCloseDialog = () => {
@ -219,6 +221,7 @@ export default inject(({ dialogsStore, uploadDataStore, filesStore }) => {
conflictResolveDialogData,
conflictResolveDialogItems: items,
setMoveToPanelVisible,
setCopyPanelVisible,
setThirdPartyMoveDialogVisible,
} = dialogsStore;
@ -234,6 +237,7 @@ export default inject(({ dialogsStore, uploadDataStore, filesStore }) => {
activeFiles,
setActiveFiles,
setMoveToPanelVisible,
setCopyPanelVisible,
setThirdPartyMoveDialogVisible,
};
})(

View File

@ -20,7 +20,12 @@ class DeleteDialogComponent extends React.Component {
let i = 0;
while (props.selection.length !== i) {
if (!(props.isRootFolder && props.selection[i].providerKey)) {
if (
!(
(props.isRootFolder && props.selection[i].providerKey) ||
props.selection[i].isEditing
)
) {
if (
props.selection[i].access === 0 ||
props.selection[i].access === 1 ||

View File

@ -207,6 +207,7 @@ export default inject(
} = dialogsStore;
const selections = selection.length ? selection : [bufferSelection];
const selectionsWithoutEditing = selections.filter((f) => !f.isEditing);
const provider = selections.find((x) => x.providerKey);
@ -220,7 +221,7 @@ export default inject(
operationsFolders,
visible: copyPanelVisible || moveToPanelVisible,
provider,
selection: selections,
selection: selectionsWithoutEditing,
setCopyPanelVisible,
setMoveToPanelVisible,

View File

@ -27,6 +27,7 @@ const FilesMediaViewer = (props) => {
setIsLoading,
setFirstLoad,
setExpandedKeys,
setToPreviewFile,
expandedKeys,
} = props;
@ -39,6 +40,25 @@ const FilesMediaViewer = (props) => {
}
}, [removeQuery, onMediaFileClick]);
useEffect(() => {
window.addEventListener("popstate", onButtonBackHandler);
}, [onButtonBackHandler]);
const onButtonBackHandler = () => {
const hash = window.location.hash;
const id = +hash.slice(9);
if (!id) {
setMediaViewerData({ visible: false, id: null });
return;
}
setMediaViewerData({ visible: true, id });
};
const onChangeUrl = (id) => {
const url = "/products/files/#preview/" + id;
window.history.pushState(null, null, url);
};
const removeQuery = (queryName) => {
const queryParams = new URLSearchParams(location.search);
@ -52,6 +72,7 @@ const FilesMediaViewer = (props) => {
const onMediaFileClick = (id) => {
//const itemId = typeof id !== "object" ? id : this.props.selection[0].id; TODO:
if (typeof id !== "object") {
const item = { visible: true, id };
setMediaViewerData(item);
@ -84,7 +105,7 @@ const FilesMediaViewer = (props) => {
}
};
const onMediaViewerClose = () => {
const onMediaViewerClose = (e) => {
if (previewFile) {
setIsLoading(true);
setFirstLoad(true);
@ -98,9 +119,19 @@ const FilesMediaViewer = (props) => {
.finally(() => {
setIsLoading(false);
setFirstLoad(false);
setToPreviewFile(null);
});
}
setMediaViewerData({ visible: false, id: null });
if (e) {
const url = localStorage.getItem("isFirstUrl");
if (!url) {
return;
}
window.history.replaceState(null, null, url);
}
};
return (
@ -121,7 +152,8 @@ const FilesMediaViewer = (props) => {
extsMediaPreviewed={mediaViewerMediaFormats} //TODO:
extsImagePreviewed={mediaViewerImageFormats} //TODO:
errorLabel={t("Translations:MediaLoadError")}
previewFile={previewFile}
isPreviewFile={!!previewFile}
onChangeUrl={onChangeUrl}
/>
)
);
@ -149,6 +181,7 @@ export default inject(
setMediaViewerData,
playlist,
previewFile,
setToPreviewFile,
} = mediaViewerDataStore;
const { deleteItemAction } = filesActionsStore;
const { media, images } = formatsStore.mediaViewersFormatsStore;
@ -171,6 +204,7 @@ export default inject(
setIsLoading,
setFirstLoad,
setExpandedKeys,
setToPreviewFile,
expandedKeys,
};
}

View File

@ -226,8 +226,6 @@ const StyledFileTileTop = styled.div`
bottom: 0;
margin: auto;
z-index: 0;
min-width: 208px;
}
.temporary-icon > .injected-svg {

View File

@ -39,17 +39,25 @@ class PureHome extends React.Component {
expandedKeys,
setExpandedKeys,
setToPreviewFile,
playlist,
mediaViewersFormatsStore,
getFileInfo,
setIsPrevSettingsModule,
isPrevSettingsModule,
} = this.props;
if (!window.location.href.includes("#preview")) {
localStorage.removeItem("isFirstUrl");
}
const reg = new RegExp(`${homepage}((/?)$|/filter)`, "gmi"); //TODO: Always find?
const match = window.location.pathname.match(reg);
let filterObj = null;
if (window.location.href.indexOf("/files/#preview") > 1) {
if (
window.location.href.indexOf("/files/#preview") > 1 &&
playlist.length < 1
) {
const pathname = window.location.href;
const fileId = pathname.slice(pathname.indexOf("#preview") + 9);
@ -70,6 +78,10 @@ class PureHome extends React.Component {
}
if (match && match.length > 0) {
if (window.location.href.includes("#preview")) {
return;
}
filterObj = FilesFilter.getFilter(window.location);
if (!filterObj) {
@ -416,7 +428,7 @@ export default inject(
? filesStore.selectionTitle
: null;
const { setToPreviewFile } = mediaViewerDataStore;
const { setToPreviewFile, playlist } = mediaViewerDataStore;
if (!firstLoad) {
if (isLoading) {
showLoader();
@ -467,6 +479,7 @@ export default inject(
setHeaderVisible: auth.settingsStore.setHeaderVisible,
personal: auth.settingsStore.personal,
setToPreviewFile,
playlist,
mediaViewersFormatsStore,
getFileInfo,

View File

@ -185,11 +185,13 @@ const StyledVersionRow = styled(Row)`
.version_link {
display: ${(props) =>
props.showEditPanel ? "none" : props.canEdit ? "block" : "none"};
text-decoration: underline dashed;
/* text-decoration: underline dashed; */
white-space: break-spaces;
margin-left: -7px;
margin-top: 4px;
cursor: ${(props) => (props.isEditing ? "default" : "pointer")};
@media ${tablet} {
display: none;
text-decoration: none;

View File

@ -36,12 +36,13 @@ const VersionRow = (props) => {
isTabletView,
onUpdateHeight,
versionsListLength,
isEditing,
} = props;
const [showEditPanel, setShowEditPanel] = useState(false);
const [commentValue, setCommentValue] = useState(info.comment);
const [isSavingComment, setIsSavingComment] = useState(false);
const canEdit = info.access === 1 || info.access === 0;
const canEdit = (info.access === 1 || info.access === 0) && !isEditing;
const title = `${new Date(info.updated).toLocaleString(
culture
@ -51,7 +52,7 @@ const VersionRow = (props) => {
const onDownloadAction = () =>
window.open(`${info.viewUrl}&version=${info.version}`, "_self");
const onEditComment = () => setShowEditPanel(!showEditPanel);
const onEditComment = () => !isEditing && setShowEditPanel(!showEditPanel);
const onChange = (e) => setCommentValue(e.target.value);
@ -117,6 +118,7 @@ const VersionRow = (props) => {
canEdit={canEdit}
isTabletView={isTabletView}
isSavingComment={isSavingComment}
isEditing={isEditing}
>
<div className={`version-row_${index}`}>
<Box displayProp="flex">
@ -205,7 +207,8 @@ const VersionRow = (props) => {
<Link
type="action"
isHovered
isHovered={!isEditing}
noHover={isEditing}
onClick={onEditComment}
className="version_link"
>
@ -276,6 +279,8 @@ export default inject(({ auth, versionHistoryStore }) => {
markAsVersion,
restoreVersion,
updateCommentVersion,
isEditing,
isEditingVersion,
} = versionHistoryStore;
return {
@ -284,6 +289,7 @@ export default inject(({ auth, versionHistoryStore }) => {
markAsVersion,
restoreVersion,
updateCommentVersion,
isEditing: isEditingVersion || isEditing,
};
})(
withRouter(

View File

@ -20,7 +20,7 @@ class SectionBodyContent extends React.Component {
}
componentDidMount() {
const { match, setFirstLoad, versions } = this.props;
const { match, setFirstLoad } = this.props;
const fileId = match.params.fileId || this.props.fileId;
if (fileId && fileId !== this.props.fileId) {
@ -138,12 +138,7 @@ class SectionBodyContent extends React.Component {
export default inject(({ auth, filesStore, versionHistoryStore }) => {
const { setFirstLoad, setIsLoading, isLoading } = filesStore;
const {
versions,
fetchFileVersions,
fileId,
setVerHistoryFileId,
} = versionHistoryStore;
const { versions, fetchFileVersions, fileId } = versionHistoryStore;
return {
culture: auth.settingsStore.culture,
@ -154,6 +149,5 @@ export default inject(({ auth, filesStore, versionHistoryStore }) => {
setFirstLoad,
setIsLoading,
fetchFileVersions,
setVerHistoryFileId,
};
})(withRouter(observer(SectionBodyContent)));

View File

@ -107,6 +107,8 @@ class FilesActionStore {
const selection = newSelection ? newSelection : this.filesStore.selection;
const currentFolderId = this.selectedFolderStore.id;
setSecondaryProgressBarData({
icon: "trash",
visible: true,
@ -148,6 +150,16 @@ class FilesActionStore {
};
await this.uploadDataStore.loopFilesOperations(data, pbData);
this.updateCurrentFolder(fileIds, folderIds);
if (currentFolderId) {
const { socketHelper } = this.authStore.settingsStore;
socketHelper.emit({
command: "refresh-folder",
data: currentFolderId,
});
}
if (isRecycleBinFolder) {
return toastr.success(translations.deleteFromTrash);
}
@ -718,6 +730,7 @@ class FilesActionStore {
canConvertSelected,
isThirdPartyRootSelection,
hasSelection,
allFilesIsEditing,
} = this.filesStore;
const { personal } = this.authStore.settingsStore;
const { userAccess } = this.filesStore;
@ -736,12 +749,16 @@ class FilesActionStore {
hasSelection &&
isAccessedSelected &&
!isRecentFolder &&
!isFavoritesFolder
!isFavoritesFolder &&
!allFilesIsEditing
);
case "delete":
const deleteCondition =
!isThirdPartyRootSelection && hasSelection && isAccessedSelected;
!isThirdPartyRootSelection &&
hasSelection &&
isAccessedSelected &&
!allFilesIsEditing;
return isCommonFolder ? userAccess && deleteCondition : deleteCondition;
}

View File

@ -81,6 +81,74 @@ class FilesStore {
this.treeFoldersStore = treeFoldersStore;
this.formatsStore = formatsStore;
this.filesSettingsStore = filesSettingsStore;
const { socketHelper } = authStore.settingsStore;
socketHelper.on("s:modify-folder", async (opt) => {
//console.log("Call s:modify-folder", opt);
if (this.isLoading) return;
switch (opt?.cmd) {
case "create":
if (opt?.type == "file" && opt?.id) {
const foundIndex = this.files.findIndex((x) => x.id === opt?.id);
if (foundIndex > -1) return;
const file = JSON.parse(opt?.data);
const newFiles = [file, ...this.files];
if (newFiles.length > this.filter.pageCount) {
newFiles.pop(); // Remove last
}
this.setFiles(newFiles);
}
break;
case "delete":
if (opt?.type == "file" && opt?.id) {
const foundIndex = this.files.findIndex((x) => x.id === opt?.id);
if (foundIndex == -1) return;
this.setFiles(
this.files.filter((_, index) => {
return index !== foundIndex;
})
);
}
break;
}
});
socketHelper.on("refresh-folder", (id) => {
if (!id || this.isLoading) return;
//console.log(
// `selected folder id ${this.selectedFolderStore.id} an changed folder id ${id}`
//);
if (this.selectedFolderStore.id == id) {
this.fetchFiles(id, this.filter);
}
});
//WAIT FOR RESPONSES OF EDITING FILE
socketHelper.on("s:start-edit-file", (id) => {
//console.log(`Call s:start-edit-file (id=${id})`);
const foundIndex = this.files.findIndex((x) => x.id === id);
if (foundIndex == -1) return;
this.updateFileStatus(foundIndex, 1);
});
socketHelper.on("s:stop-edit-file", (id) => {
//console.log(`Call s:stop-edit-file (id=${id})`);
const foundIndex = this.files.findIndex((x) => x.id === id);
if (foundIndex == -1) return;
this.updateFileStatus(foundIndex, 0);
});
}
setIsPrevSettingsModule = (isSettings) => {
@ -212,13 +280,35 @@ class FilesStore {
};
setFiles = (files) => {
const { socketHelper } = this.settingsStore;
if (this.files?.length > 0) {
socketHelper.emit({
command: "unsubscribe",
data: this.files.map((f) => `FILE-${f.id}`),
});
}
this.files = files;
if (this.files?.length > 0) {
socketHelper.emit({
command: "subscribe",
data: this.files.map((f) => `FILE-${f.id}`),
});
}
};
setFolders = (folders) => {
this.folders = folders;
};
updateFileStatus = (index, status) => {
if (index < 0) return;
this.files[index].fileStatus = status;
};
setFile = (file) => {
const index = this.files.findIndex((x) => x.id === file.id);
if (index !== -1) this.files[index] = file;
@ -383,6 +473,23 @@ class FilesStore {
!isRecycleBinFolder && this.checkUpdateNode(data, folderId);
filterData.total = data.total;
if (data.total > 0) {
const lastPage = filterData.getLastPage();
if (filterData.page > lastPage) {
filterData.page = lastPage;
return this.fetchFiles(
folderId,
filterData,
clearFilter,
withSubfolders
);
}
}
if (!isRecycleBinFolder && withSubfolders) {
const path = data.pathParts.slice(0);
const foldersCount = data.current.foldersCount;
@ -393,7 +500,6 @@ class FilesStore {
const isPrivacyFolder =
data.current.rootFolderType === FolderType.Privacy;
filterData.total = data.total;
this.setFilesFilter(filterData, isPrefSettings); //TODO: FILTER
this.setFolders(isPrivacyFolder && isMobile ? [] : data.folders);
this.setFiles(isPrivacyFolder && isMobile ? [] : data.files);
@ -507,7 +613,7 @@ class FilesStore {
const canConvert = false; //TODO: fix of added convert check;
const isEncrypted = item.encrypted;
const isDocuSign = false; //TODO: need this prop;
const isEditing = false; //TODO: need this prop;
const isEditing = item.fileStatus === 1;
const isFileOwner = item.createdBy.id === this.userStore.user.id;
const {
@ -621,6 +727,7 @@ class FilesStore {
fileOptions = this.removeOptions(fileOptions, [
"finalize-version",
"move-to",
"separator2",
"delete",
]);
if (isThirdPartyFolder) {
@ -1282,6 +1389,7 @@ class FilesStore {
: null;
const needConvert = canConvert(fileExst);
const isEditing = item.fileStatus === 1;
const docUrl = combineUrl(
AppServerConfig.proxyURL,
@ -1341,6 +1449,7 @@ class FilesStore {
folderUrl,
href,
isThirdPartyFolder,
isEditing,
};
});
@ -1548,6 +1657,16 @@ class FilesStore {
return filesList.length <= 0;
}
get allFilesIsEditing() {
const hasFolders = this.selection.find(
(x) => !x.fileExst || !x.contentLength
);
if (!hasFolders) {
return this.selection.every((x) => x.isEditing);
}
return false;
}
getOptions = (selection, externalAccess = false) => {
const {
canWebEdit,
@ -1619,9 +1738,15 @@ class FilesStore {
if (fileType === "file") {
this.activeFiles.findIndex((f) => f == id) === -1 &&
newSelection.push(this.files.find((f) => f.id == id));
//newSelection.push(this.files.find((f) => f.id == id));
newSelection.push(
this.filesList.find((f) => f.id == id && f.fileExst)
);
} else if (this.activeFolders.findIndex((f) => f == id) === -1) {
const selectableFolder = this.folders.find((f) => f.id == id);
//const selectableFolder = this.folders.find((f) => f.id == id);
const selectableFolder = this.filesList.find(
(f) => f.id == id && !f.fileExst
);
selectableFolder.isFolder = true;
newSelection.push(selectableFolder);
}
@ -1705,6 +1830,10 @@ class FilesStore {
return fileInfo;
};
openDocEditor = (id, providerKey = null, tab = null, url = null) => {
return openEditor(id, providerKey, tab, url);
};
getFolderInfo = async (id) => {
const folderInfo = await api.files.getFolderInfo(id);
this.setFolder(folderInfo);

View File

@ -20,7 +20,15 @@ class MediaViewerDataStore {
};
setToPreviewFile = (file, visible) => {
if (file === null) {
this.previewFile = null;
this.id = null;
this.visible = false;
return;
}
if (!file.canOpenPlayer) return;
this.previewFile = file;
this.id = file.id;
this.visible = visible;
@ -55,6 +63,7 @@ class MediaViewerDataStore {
title: this.previewFile.title,
});
}
return playlist;
}
}

View File

@ -19,8 +19,11 @@ class SelectedFolderStore {
pathParts = null;
providerItem = null;
constructor() {
settingsStore = null;
constructor(settingsStore) {
makeAutoObservable(this);
this.settingsStore = settingsStore;
}
get isRootFolder() {
@ -48,6 +51,19 @@ class SelectedFolderStore {
};
setSelectedFolder = (selectedFolder) => {
const { socketHelper } = this.settingsStore;
if (this.id !== null) {
socketHelper.emit({ command: "unsubscribe", data: `DIR-${this.id}` });
}
if (selectedFolder) {
socketHelper.emit({
command: "subscribe",
data: `DIR-${selectedFolder.id}`,
});
}
if (!selectedFolder) {
this.toDefault();
} else {
@ -62,4 +78,4 @@ class SelectedFolderStore {
};
}
export default new SelectedFolderStore();
export default SelectedFolderStore;

View File

@ -531,7 +531,7 @@ class UploadDataStore {
if (window.location.pathname.indexOf("/history") === -1) {
const newFiles = files;
const newFolders = folders;
const path = currentFile.path || [];
const path = currentFile.path.slice() || [];
const fileIndex = newFiles.findIndex(
(x) => x.id === currentFile.fileInfo.id
);
@ -886,6 +886,18 @@ class UploadDataStore {
conversionPercent: 0,
};
if (this.files.length > 0) {
const toFolderId = this.files[0]?.toFolderId;
if (toFolderId) {
const { socketHelper } = this.filesStore.settingsStore;
socketHelper.emit({
command: "refresh-folder",
data: toFolderId,
});
}
}
setTimeout(() => {
if (!this.primaryProgressDataStore.alert) {
this.primaryProgressDataStore.clearPrimaryProgressData();

View File

@ -1,5 +1,6 @@
import { makeObservable, action, observable } from "mobx";
import { makeAutoObservable, runInAction } from "mobx";
import api from "@appserver/common/api";
import { size } from "@appserver/components/utils/device";
class VersionHistoryStore {
isVisible = false;
@ -8,22 +9,42 @@ class VersionHistoryStore {
filesStore = null;
showProgressBar = false;
timerId = null;
isEditing = false;
constructor(filesStore) {
makeObservable(this, {
isVisible: observable,
fileId: observable,
versions: observable,
showProgressBar: observable,
setIsVerHistoryPanel: action,
setVerHistoryFileId: action,
setVerHistoryFileVersions: action,
markAsVersion: action,
restoreVersion: action,
updateCommentVersion: action,
});
makeAutoObservable(this);
this.filesStore = filesStore;
const isTabletView = window.innerWidth <= size.tablet;
if (isTabletView && this.versions) {
//TODO: Files store in not initialized on versionHistory page. Need socket.
const { socketHelper } = this.filesStore.settingsStore;
socketHelper.on("s:start-edit-file", (id) => {
//console.log(`VERSION STORE Call s:start-edit-file (id=${id})`);
const verIndex = this.versions.findIndex((x) => x.id == id);
if (verIndex == -1) return;
runInAction(() => (this.isEditing = true));
});
socketHelper.on("s:stop-edit-file", (id) => {
//console.log(`VERSION STORE Call s:stop-edit-file (id=${id})`);
const verIndex = this.files.findIndex((x) => x.id === id);
if (verIndex == -1) return;
runInAction(() => (this.isEditing = false));
});
}
}
get isEditingVersion() {
if (this.fileId && this.filesStore.files.length) {
const file = this.filesStore.files.find((x) => x.id === +this.fileId);
return file ? file.fileStatus === 1 : false;
}
return false;
}
setIsVerHistoryPanel = (isVisible) => {

View File

@ -1,6 +1,6 @@
import FilesStore from "./FilesStore";
import fileActionStore from "./FileActionStore";
import selectedFolderStore from "./SelectedFolderStore";
import SelectedFolderStore from "./SelectedFolderStore";
import TreeFoldersStore from "./TreeFoldersStore";
import thirdPartyStore from "./ThirdPartyStore";
import SettingsStore from "./SettingsStore";
@ -24,6 +24,9 @@ const formatsStore = new FormatsStore(
mediaViewersFormatsStore,
docserviceStore
);
const selectedFolderStore = new SelectedFolderStore(store.auth.settingsStore);
const treeFoldersStore = new TreeFoldersStore(selectedFolderStore);
const settingsStore = new SettingsStore(thirdPartyStore, treeFoldersStore);
@ -60,6 +63,7 @@ const uploadDataStore = new UploadDataStore(
dialogsStore,
settingsStore
);
const filesActionsStore = new FilesActionsStore(
store.auth,
uploadDataStore,

View File

@ -1,29 +1,30 @@
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ModuleFederationPlugin = require('webpack').container.ModuleFederationPlugin;
const ExternalTemplateRemotesPlugin = require('external-remotes-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const combineUrl = require('@appserver/common/utils/combineUrl');
const AppServerConfig = require('@appserver/common/constants/AppServerConfig');
const sharedDeps = require('@appserver/common/constants/sharedDependencies');
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const ModuleFederationPlugin = require("webpack").container
.ModuleFederationPlugin;
const ExternalTemplateRemotesPlugin = require("external-remotes-plugin");
const TerserPlugin = require("terser-webpack-plugin");
const combineUrl = require("@appserver/common/utils/combineUrl");
const AppServerConfig = require("@appserver/common/constants/AppServerConfig");
const sharedDeps = require("@appserver/common/constants/sharedDependencies");
const path = require('path');
const pkg = require('./package.json');
const path = require("path");
const pkg = require("./package.json");
const deps = pkg.dependencies || {};
const homepage = pkg.homepage; // combineUrl(AppServerConfig.proxyURL, pkg.homepage);
const title = pkg.title;
var config = {
mode: 'development',
entry: './src/index',
mode: "development",
entry: "./src/index",
devServer: {
devMiddleware: {
publicPath: homepage,
},
static: {
directory: path.join(__dirname, 'dist'),
directory: path.join(__dirname, "dist"),
publicPath: homepage,
},
port: 5008,
@ -35,22 +36,23 @@ var config = {
},
hot: false,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'X-Requested-With, content-type, Authorization',
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
"Access-Control-Allow-Headers":
"X-Requested-With, content-type, Authorization",
},
},
output: {
publicPath: 'auto',
chunkFilename: 'static/js/[id].[contenthash].js',
publicPath: "auto",
chunkFilename: "static/js/[id].[contenthash].js",
//assetModuleFilename: "assets/[hash][ext][query]",
path: path.resolve(process.cwd(), 'dist'),
filename: 'static/js/[name].[contenthash].bundle.js',
path: path.resolve(process.cwd(), "dist"),
filename: "static/js/[name].[contenthash].bundle.js",
},
resolve: {
extensions: ['.jsx', '.js', '.json'],
extensions: [".jsx", ".js", ".json"],
fallback: {
crypto: false,
},
@ -65,14 +67,14 @@ var config = {
rules: [
{
test: /\.(png|jpe?g|gif|ico)$/i,
type: 'asset/resource',
type: "asset/resource",
generator: {
filename: 'static/images/[hash][ext][query]',
filename: "static/images/[hash][ext][query]",
},
},
{
test: /\.m?js/,
type: 'javascript/auto',
type: "javascript/auto",
resolve: {
fullySpecified: false,
},
@ -81,7 +83,7 @@ var config = {
test: /\.react.svg$/,
use: [
{
loader: '@svgr/webpack',
loader: "@svgr/webpack",
options: {
svgoConfig: {
plugins: [{ removeViewBox: false }],
@ -90,26 +92,26 @@ var config = {
},
],
},
{ test: /\.json$/, loader: 'json-loader' },
{ test: /\.json$/, loader: "json-loader" },
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
use: ["style-loader", "css-loader"],
},
{
test: /\.s[ac]ss$/i,
use: [
// Creates `style` nodes from JS strings
'style-loader',
"style-loader",
// Translates CSS into CommonJS
{
loader: 'css-loader',
loader: "css-loader",
options: {
url: {
filter: (url, resourcePath) => {
// resourcePath - path to css file
// Don't handle `/static` urls
if (url.startsWith('/static') || url.startsWith('data:')) {
if (url.startsWith("/static") || url.startsWith("data:")) {
return false;
}
@ -119,7 +121,7 @@ var config = {
},
},
// Compiles Sass to CSS
'sass-loader',
"sass-loader",
],
},
@ -128,17 +130,17 @@ var config = {
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
loader: "babel-loader",
options: {
presets: ['@babel/preset-react', '@babel/preset-env'],
presets: ["@babel/preset-react", "@babel/preset-env"],
plugins: [
'@babel/plugin-transform-runtime',
'@babel/plugin-proposal-class-properties',
'@babel/plugin-proposal-export-default-from',
"@babel/plugin-transform-runtime",
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-export-default-from",
],
},
},
'source-map-loader',
"source-map-loader",
],
},
],
@ -147,18 +149,24 @@ var config = {
plugins: [
new CleanWebpackPlugin(),
new ModuleFederationPlugin({
name: 'files',
filename: 'remoteEntry.js',
name: "files",
filename: "remoteEntry.js",
remotes: {
studio: `studio@${combineUrl(AppServerConfig.proxyURL, '/remoteEntry.js')}`,
people: `people@${combineUrl(AppServerConfig.proxyURL, '/products/people/remoteEntry.js')}`,
studio: `studio@${combineUrl(
AppServerConfig.proxyURL,
"/remoteEntry.js"
)}`,
people: `people@${combineUrl(
AppServerConfig.proxyURL,
"/products/people/remoteEntry.js"
)}`,
},
exposes: {
'./app': './src/Files.jsx',
'./SharingDialog': './src/components/panels/SharingDialog',
'./utils': './src/helpers/utils.js',
'./SelectFileDialog': './src/components/panels/SelectFileDialog',
'./SelectFolderDialog': './src/components/panels/SelectFolderDialog',
"./app": "./src/Files.jsx",
"./SharingDialog": "./src/components/panels/SharingDialog",
"./utils": "./src/helpers/utils.js",
"./SelectFileDialog": "./src/components/panels/SelectFileDialog",
"./SelectFolderDialog": "./src/components/panels/SelectFolderDialog",
},
shared: {
...deps,
@ -169,11 +177,11 @@ var config = {
new CopyPlugin({
patterns: [
{
from: 'public',
from: "public",
globOptions: {
dot: true,
gitignore: true,
ignore: ['**/index.html'],
ignore: ["**/index.html"],
},
},
],
@ -186,7 +194,7 @@ module.exports = (env, argv) => {
config.plugins = [
...config.plugins,
new HtmlWebpackPlugin({
template: './public/index.html',
template: "./public/index.html",
publicPath: homepage,
title: title,
base: `${homepage}/`,
@ -212,23 +220,23 @@ module.exports = (env, argv) => {
config.plugins = [
...config.plugins,
new HtmlWebpackPlugin({
template: './public/index.html',
template: "./public/index.html",
publicPath: homepage,
title: title,
base: `${homepage}/`,
custom: '',
custom: "",
}),
];
}
if (argv.mode === 'production') {
config.mode = 'production';
if (argv.mode === "production") {
config.mode = "production";
config.optimization = {
splitChunks: { chunks: 'all' },
splitChunks: { chunks: "all" },
minimize: !env.minimize,
minimizer: [new TerserPlugin()],
};
} else {
config.devtool = 'cheap-module-source-map';
config.devtool = "cheap-module-source-map";
}
return config;

View File

@ -689,6 +689,8 @@ namespace ASC.Web.Files.Services.WCFService
FileMarker.MarkAsNew(file);
SocketManager.CreateFile(file);
return file;
}
@ -709,7 +711,7 @@ namespace ASC.Web.Files.Services.WCFService
if (isFinish)
{
FileTracker.Remove(id, tabId);
SocketManager.FilesChangeEditors(id, true);
SocketManager.StopEdit(id);
}
else
{
@ -763,7 +765,6 @@ namespace ASC.Web.Files.Services.WCFService
if (file != null)
FilesMessageService.Send(file, GetHttpHeaders(), MessageAction.FileUpdated, file.Title);
SocketManager.FilesChangeEditors(fileId, !forcesave);
return file;
}
catch (Exception e)
@ -793,7 +794,6 @@ namespace ASC.Web.Files.Services.WCFService
if (file != null)
FilesMessageService.Send(file, GetHttpHeaders(), MessageAction.FileUpdated, file.Title);
SocketManager.FilesChangeEditors(fileId, true);
return file;
}
catch (Exception e)

View File

@ -83,8 +83,9 @@ namespace ASC.Web.Files.HttpHandlers
private SetupInfo SetupInfo { get; }
private InstanceCrypto InstanceCrypto { get; }
private ChunkedUploadSessionHolder ChunkedUploadSessionHolder { get; }
private ChunkedUploadSessionHelper ChunkedUploadSessionHelper { get; }
public ILog Logger { get; }
private ChunkedUploadSessionHelper ChunkedUploadSessionHelper { get; }
private SocketManager SocketManager { get; }
private ILog Logger { get; }
public ChunkedUploaderHandlerService(
IOptionsMonitor<ILog> optionsMonitor,
@ -96,7 +97,8 @@ namespace ASC.Web.Files.HttpHandlers
SetupInfo setupInfo,
InstanceCrypto instanceCrypto,
ChunkedUploadSessionHolder chunkedUploadSessionHolder,
ChunkedUploadSessionHelper chunkedUploadSessionHelper)
ChunkedUploadSessionHelper chunkedUploadSessionHelper,
SocketManager socketManager)
{
TenantManager = tenantManager;
FileUploader = fileUploader;
@ -106,7 +108,8 @@ namespace ASC.Web.Files.HttpHandlers
SetupInfo = setupInfo;
InstanceCrypto = instanceCrypto;
ChunkedUploadSessionHolder = chunkedUploadSessionHolder;
ChunkedUploadSessionHelper = chunkedUploadSessionHelper;
ChunkedUploadSessionHelper = chunkedUploadSessionHelper;
SocketManager = socketManager;
Logger = optionsMonitor.CurrentValue;
}
@ -169,6 +172,8 @@ namespace ASC.Web.Files.HttpHandlers
{
await WriteSuccess(context, ToResponseObject(resumedSession.File), (int)HttpStatusCode.Created);
FilesMessageService.Send(resumedSession.File, MessageAction.FileUploaded, resumedSession.File.Title);
SocketManager.CreateFile(resumedSession.File);
}
else
{

View File

@ -117,7 +117,8 @@ namespace ASC.Web.Files
private FFmpegService FFmpegService { get; }
private IServiceProvider ServiceProvider { get; }
public TempStream TempStream { get; }
private UserManager UserManager { get; }
private UserManager UserManager { get; }
private SocketManager SocketManager { get; }
private ILog Logger { get; }
public FileHandlerService(
@ -139,13 +140,15 @@ namespace ASC.Web.Files
GlobalFolderHelper globalFolderHelper,
PathProvider pathProvider,
UserManager userManager,
DocumentServiceTrackerHelper documentServiceTrackerHelper,
DocumentServiceTrackerHelper documentServiceTrackerHelper,
DocumentServiceHelper documentServiceHelper,
FilesMessageService filesMessageService,
FileShareLink fileShareLink,
FileConverter fileConverter,
FFmpegService fFmpegService,
IServiceProvider serviceProvider,
TempStream tempStream)
TempStream tempStream,
SocketManager socketManager)
{
FilesLinkUtility = filesLinkUtility;
TenantExtra = tenantExtra;
@ -168,6 +171,7 @@ namespace ASC.Web.Files
FileConverter = fileConverter;
FFmpegService = fFmpegService;
ServiceProvider = serviceProvider;
SocketManager = socketManager;
TempStream = tempStream;
UserManager = userManager;
Logger = optionsMonitor.CurrentValue;
@ -1095,7 +1099,9 @@ namespace ASC.Web.Files
{
var docType = context.Request.Query["doctype"];
file = CreateFileFromTemplate(folder, fileTitle, docType);
}
}
SocketManager.CreateFile(file);
}
catch (Exception ex)
{

View File

@ -62,8 +62,8 @@ namespace ASC.Web.Files.Services.DocumentService
private LockerManager LockerManager { get; }
private FileTrackerHelper FileTracker { get; }
private EntryStatusManager EntryStatusManager { get; }
private IServiceProvider ServiceProvider { get; }
private IServiceProvider ServiceProvider { get; }
public DocumentServiceHelper(
IDaoFactory daoFactory,
FileShareLink fileShareLink,
@ -93,7 +93,7 @@ namespace ASC.Web.Files.Services.DocumentService
LockerManager = lockerManager;
FileTracker = fileTracker;
EntryStatusManager = entryStatusManager;
ServiceProvider = serviceProvider;
ServiceProvider = serviceProvider;
}
public File<T> GetParams<T>(T fileId, int version, string doc, bool editPossible, bool tryEdit, bool tryCoauth, out Configuration<T> configuration)

View File

@ -249,8 +249,9 @@ namespace ASC.Web.Files.Services.DocumentService
{
case TrackerStatus.NotFound:
case TrackerStatus.Closed:
FileTracker.Remove(fileId);
SocketManager.FilesChangeEditors(fileId, true);
FileTracker.Remove(fileId);
SocketManager.StopEdit(fileId);
break;
case TrackerStatus.Editing:
@ -282,10 +283,8 @@ namespace ASC.Web.Files.Services.DocumentService
string docKey;
var app = ThirdPartySelector.GetAppByFileId(fileId.ToString());
if (app == null)
{
File<T> fileStable;
fileStable = DaoFactory.GetFileDao<T>().GetFileStable(fileId);
{
File<T> fileStable = DaoFactory.GetFileDao<T>().GetFileStable(fileId);
docKey = DocumentServiceHelper.GetDocKey(fileStable);
}
else
@ -333,8 +332,9 @@ namespace ASC.Web.Files.Services.DocumentService
foreach (var removeUserId in users)
{
FileTracker.Remove(fileId, userId: removeUserId);
}
SocketManager.FilesChangeEditors(fileId);
}
SocketManager.StartEdit(fileId);
}
private TrackResponse ProcessSave<T>(T fileId, TrackerData fileData)
@ -460,8 +460,6 @@ namespace ASC.Web.Files.Services.DocumentService
SaveHistory(file, (fileData.History ?? "").ToString(), DocumentServiceConnector.ReplaceDocumentAdress(fileData.ChangesUrl));
}
SocketManager.FilesChangeEditors(fileId, !forcesave);
var result = new TrackResponse { Message = saveMessage };
return result;
}

View File

@ -173,8 +173,8 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
if (FolderDao.IsEmpty(folder.ID))
{
FolderDao.DeleteFolder(folder.ID);
filesMessageService.Send(folder, _headers, MessageAction.FolderDeleted, folder.Title);
filesMessageService.Send(folder, _headers, MessageAction.FolderDeleted, folder.Title);
ProcessedFolder(folderId);
}
}
@ -209,7 +209,9 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
private void DeleteFiles(IEnumerable<T> fileIds, IServiceScope scope)
{
var scopeClass = scope.ServiceProvider.GetService<FileDeleteOperationScope>();
var scopeClass = scope.ServiceProvider.GetService<FileDeleteOperationScope>();
var socketManager = scope.ServiceProvider.GetService<SocketManager>();
var (fileMarker, filesMessageService) = scopeClass;
foreach (var fileId in fileIds)
{
@ -236,14 +238,18 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
{
file.ThumbnailStatus = Thumbnail.NotRequired;
FileDao.SaveThumbnail(file, null);
}
}
socketManager.DeleteFile(file);
}
else
{
try
{
FileDao.DeleteFile(file.ID);
filesMessageService.Send(file, _headers, MessageAction.FileDeleted, file.Title);
filesMessageService.Send(file, _headers, MessageAction.FileDeleted, file.Title);
socketManager.DeleteFile(file);
}
catch (Exception ex)
{

View File

@ -185,7 +185,7 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
var scopeClass = scope.ServiceProvider.GetService<FileMoveCopyOperationScope>();
var (filesMessageService, fileMarker, _, _, _) = scopeClass;
var folderDao = scope.ServiceProvider.GetService<IFolderDao<TTo>>();
var folderDao = scope.ServiceProvider.GetService<IFolderDao<TTo>>();
var toFolderId = toFolder.ID;
var isToFolder = Equals(toFolderId, DaoFolderId);
@ -256,7 +256,8 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
}
else if (FolderDao.IsEmpty(folder.ID))
{
FolderDao.DeleteFolder(folder.ID);
FolderDao.DeleteFolder(folder.ID);
if (ProcessedFolder(folderId))
{
Result += string.Format("folder_{0}{1}", newFolder.ID, SPLIT_CHAR);
@ -276,7 +277,7 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
filesMessageService.Send(newFolder, toFolder, _headers, MessageAction.FolderCopiedWithOverwriting, newFolder.Title, toFolder.Title);
if (isToFolder)
needToMark.Add(newFolder);
needToMark.Add(newFolder);
if (ProcessedFolder(folderId))
{
@ -376,7 +377,8 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
var scopeClass = scope.ServiceProvider.GetService<FileMoveCopyOperationScope>();
var (filesMessageService, fileMarker, fileUtility, global, entryManager) = scopeClass;
var fileDao = scope.ServiceProvider.GetService<IFileDao<TTo>>();
var fileTracker = scope.ServiceProvider.GetService<FileTrackerHelper>();
var fileTracker = scope.ServiceProvider.GetService<FileTrackerHelper>();
var socketManager = scope.ServiceProvider.GetService<SocketManager>();
var toFolderId = toFolder.ID;
foreach (var fileId in fileIds)
@ -424,7 +426,9 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
if (Equals(newFile.FolderID.ToString(), DaoFolderId))
{
needToMark.Add(newFile);
}
}
socketManager.CreateFile(newFile);
if (ProcessedFile(fileId))
{
@ -477,6 +481,10 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
{
needToMark.Add(newFile);
}
socketManager.DeleteFile(file);
socketManager.CreateFile(newFile);
if (ProcessedFile(fileId))
{
@ -531,6 +539,8 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
LinkDao.DeleteAllLink(newFile.ID.ToString());
needToMark.Add(newFile);
socketManager.CreateFile(newFile);
if (copy)
{
@ -568,7 +578,9 @@ namespace ASC.Web.Files.Services.WCFService.FileOperations
else
{
filesMessageService.Send(newFile, toFolder, _headers, MessageAction.FileMovedWithOverwriting, file.Title, parentFolder.Title, toFolder.Title);
}
}
socketManager.DeleteFile(file);
if (ProcessedFile(fileId))
{

View File

@ -24,30 +24,83 @@
*/
using System.Text.Json;
using ASC.Api.Core;
using ASC.Api.Documents;
using ASC.Common;
using ASC.Core;
using ASC.Core;
using ASC.Core.Notify.Signalr;
using Microsoft.Extensions.Options;
using ASC.Files.Core;
using Microsoft.Extensions.Options;
namespace ASC.Web.Files.Utils
{
[Scope]
public class SocketManager
{
private readonly SignalrServiceClient _signalrServiceClient;
public SocketManager(IOptionsSnapshot<SignalrServiceClient> optionsSnapshot, TenantManager tenantManager)
private readonly SignalrServiceClient _signalrServiceClient;
private FileWrapperHelper FilesWrapperHelper { get; }
private TenantManager TenantManager { get; }
public SocketManager(
IOptionsSnapshot<SignalrServiceClient> optionsSnapshot,
FileWrapperHelper filesWrapperHelper,
TenantManager tenantManager
)
{
_signalrServiceClient = optionsSnapshot.Get("files");
TenantManager = tenantManager;
_signalrServiceClient = optionsSnapshot.Get("files");
FilesWrapperHelper = filesWrapperHelper;
TenantManager = tenantManager;
}
public void StartEdit<T>(T fileId)
{
var room = GetFileRoom(fileId);
_signalrServiceClient.StartEdit(fileId, room);
}
public void StopEdit<T>(T fileId)
{
var room = GetFileRoom(fileId);
_signalrServiceClient.StopEdit(fileId, room);
}
public void CreateFile<T>(File<T> file)
{
var room = GetFolderRoom(file.FolderID);
var serializerSettings = new JsonSerializerOptions()
{
WriteIndented = false,
IgnoreNullValues = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
serializerSettings.Converters.Add(new ApiDateTimeConverter());
serializerSettings.Converters.Add(new FileEntryWrapperConverter());
var data = JsonSerializer.Serialize(FilesWrapperHelper.Get(file), serializerSettings);
_signalrServiceClient.CreateFile(file.ID, room, data);
}
public void DeleteFile<T>(File<T> file)
{
var room = GetFolderRoom(file.FolderID);
_signalrServiceClient.DeleteFile(file.ID, room);
}
private string GetFileRoom<T>(T fileId)
{
var tenantId = TenantManager.GetCurrentTenant().TenantId;
private TenantManager TenantManager { get; }
return $"{tenantId}-FILE-{fileId}";
}
private string GetFolderRoom<T>(T folderId)
{
var tenantId = TenantManager.GetCurrentTenant().TenantId;
public void FilesChangeEditors(object fileId, bool finish = false)
{
_signalrServiceClient.FilesChangeEditors(TenantManager.GetCurrentTenant().TenantId, fileId.ToString(), finish);
return $"{tenantId}-DIR-{folderId}";
}
}
}

View File

@ -1230,6 +1230,10 @@ namespace ASC.Api.Documents
[Update("file/{fileId}/checkconversion")]
public IEnumerable<ConversationResult<string>> StartConversion(string fileId, [FromBody(EmptyBodyBehavior = Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior.Allow)] CheckConversionModel<string> model)
{
if (model == null)
{
model = new CheckConversionModel<string>();
}
model.FileId = fileId;
return FilesControllerHelperString.StartConversion(model);
}
@ -1237,6 +1241,10 @@ namespace ASC.Api.Documents
[Update("file/{fileId:int}/checkconversion")]
public IEnumerable<ConversationResult<int>> StartConversion(int fileId, [FromBody(EmptyBodyBehavior = Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior.Allow)] CheckConversionModel<int> model)
{
if (model == null)
{
model = new CheckConversionModel<int>();
}
model.FileId = fileId;
return FilesControllerHelperInt.StartConversion(model);
}

@ -1 +1 @@
Subproject commit b9a3b81a255ccaa6f174e7ad2dd3bf4d4911aa8f
Subproject commit 2bb4d878b42e8c648dd44fed7d445163772551fa

View File

@ -71,6 +71,7 @@ namespace ASC.Files.Helpers
private ApiDateTimeHelper ApiDateTimeHelper { get; }
private UserManager UserManager { get; }
private DisplayUserSettingsHelper DisplayUserSettingsHelper { get; }
public SocketManager SocketManager { get; }
public IServiceProvider ServiceProvider { get; }
private ILog Logger { get; set; }
@ -104,7 +105,8 @@ namespace ASC.Files.Helpers
ApiDateTimeHelper apiDateTimeHelper,
UserManager userManager,
DisplayUserSettingsHelper displayUserSettingsHelper,
IServiceProvider serviceProvider)
IServiceProvider serviceProvider,
SocketManager socketManager)
{
ApiContext = context;
FileStorageService = fileStorageService;
@ -129,6 +131,7 @@ namespace ASC.Files.Helpers
UserManager = userManager;
DisplayUserSettingsHelper = displayUserSettingsHelper;
ServiceProvider = serviceProvider;
SocketManager = socketManager;
HttpContextAccessor = httpContextAccessor;
FileConverter = fileConverter;
Logger = optionMonitor.Get("ASC.Files");
@ -187,6 +190,9 @@ namespace ASC.Files.Helpers
try
{
var resultFile = FileUploader.Exec(folderId, title, file.Length, file, createNewIfExist ?? !FilesSettingsHelper.UpdateIfExist, !keepConvertStatus);
SocketManager.CreateFile(resultFile);
return FileWrapperHelper.Get(resultFile);
}
catch (FileNotFoundException e)
@ -328,6 +334,7 @@ namespace ASC.Files.Helpers
public FolderWrapper<T> CreateFolder(T folderId, string title)
{
var folder = FileStorageService.CreateNewFolder(folderId, title);
return FolderWrapperHelper.Get(folder);
}
@ -440,7 +447,7 @@ namespace ASC.Files.Helpers
public IEnumerable<ConversationResult<T>> CheckConversion(CheckConversionModel<T> model)
{
return FileStorageService.CheckConversion(new List<CheckConversionModel<T>>() { model })
return FileStorageService.CheckConversion(new List<CheckConversionModel<T>>() { model }, model.Sync)
.Select(r =>
{
var o = new ConversationResult<T>

View File

@ -321,6 +321,8 @@ namespace ASC.Api.Settings
settings.OwnerId = Tenant.OwnerId;
settings.NameSchemaId = CustomNamingPeople.Current.Id;
settings.SocketUrl = Configuration["web:hub:url"] ?? "";
settings.Firebase = new FirebaseWrapper
{
ApiKey = Configuration["firebase:apiKey"] ?? "",

View File

@ -73,6 +73,8 @@ namespace ASC.Api.Settings
public bool DebugInfo { get; set; }
public string SocketUrl { get; set; }
public static SettingsWrapper GetSample()
{
return new SettingsWrapper

View File

@ -81,9 +81,9 @@ const Editor = () => {
const decodedId = urlParams
? urlParams.fileId || urlParams.fileid || null
: null;
const fileId =
let fileId =
typeof decodedId === "string" ? encodeURIComponent(decodedId) : decodedId;
const version = urlParams ? urlParams.version || null : null;
let version = urlParams ? urlParams.version || null : null;
const doc = urlParams ? urlParams.doc || null : null;
const isDesktop = window["AscDesktopEditor"] !== undefined;
const view = url.indexOf("action=view") !== -1;
@ -183,7 +183,7 @@ const Editor = () => {
const convertDocumentUrl = async () => {
const convert = await convertFile(fileId, null, true);
return convert[0]?.result;
return convert && convert[0]?.result;
};
const init = async () => {
@ -233,9 +233,15 @@ const Editor = () => {
if (canConvert(fileInfo.fileExst)) {
const result = await convertDocumentUrl();
const splitUrl = url.split("#message/");
if (result) {
history.pushState({}, null, result.webUrl);
url = window.location.href;
const newUrl = `${result.webUrl}#message/${splitUrl[1]}`;
history.pushState({}, null, newUrl);
fileInfo = result;
url = newUrl;
fileId = result.id;
version = result.version;
}
@ -260,7 +266,8 @@ const Editor = () => {
try {
const formUrl = await checkFillFormDraft(fileId);
history.pushState({}, null, formUrl);
url = window.location.href;
document.location.reload();
} catch (err) {
console.error(err);
}
@ -614,7 +621,10 @@ const Editor = () => {
let convertUrl = url.substring(0, index);
if (canConvert(fileInfo.fileExst)) {
convertUrl = await convertDocumentUrl();
const newUrl = await convertDocumentUrl();
if (newUrl) {
convertUrl = newUrl.webUrl;
}
}
history.pushState({}, null, convertUrl);
@ -703,7 +713,9 @@ const Editor = () => {
const onlyNumbers = new RegExp("^[0-9]+$");
const isFileWithoutProvider = onlyNumbers.test(fileId);
const convertFileId = isFileWithoutProvider ? +fileId : fileId;
const convertFileId = isFileWithoutProvider
? +fileId
: decodeURIComponent(fileId);
favorite
? markAsFavorite([convertFileId])

579
yarn.lock

File diff suppressed because it is too large Load Diff