Merge branch 'hotfix/v1.1.1' into hotfix/campaigns-banner
This commit is contained in:
commit
3a16fdccfd
@ -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);
|
||||
|
@ -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>
|
@ -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;
|
@ -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();
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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 };
|
||||
}
|
@ -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 };
|
||||
}
|
@ -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 };
|
||||
};
|
||||
|
@ -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 };
|
||||
}
|
@ -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;
|
@ -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;
|
@ -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,
|
||||
});
|
||||
|
@ -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"));
|
||||
});
|
||||
};
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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 "";
|
||||
};
|
||||
|
47
common/ASC.Socket.IO/app/requestManager.js
Normal file
47
common/ASC.Socket.IO/app/requestManager.js
Normal 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);
|
||||
};
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
99
common/ASC.Socket.IO/server.js
Normal file
99
common/ASC.Socket.IO/server.js
Normal 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;
|
@ -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
@ -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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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": {
|
||||
|
@ -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;
|
||||
|
@ -213,6 +213,10 @@ class FilesFilter {
|
||||
return str;
|
||||
};
|
||||
|
||||
getLastPage() {
|
||||
return Math.ceil(this.total / this.pageCount) - 1;
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new FilesFilter(
|
||||
this.page,
|
||||
|
@ -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;
|
||||
|
@ -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"
|
||||
},
|
||||
|
@ -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);
|
||||
|
59
packages/asc-web-common/utils/socket.js
Normal file
59
packages/asc-web-common/utils/socket.js
Normal 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;
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
};
|
||||
})(
|
||||
|
@ -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 ||
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -226,8 +226,6 @@ const StyledFileTileTop = styled.div`
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
z-index: 0;
|
||||
|
||||
min-width: 208px;
|
||||
}
|
||||
|
||||
.temporary-icon > .injected-svg {
|
||||
|
@ -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,
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
@ -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)));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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) => {
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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))
|
||||
{
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
@ -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>
|
||||
|
@ -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"] ?? "",
|
||||
|
@ -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
|
||||
|
@ -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])
|
||||
|
Loading…
Reference in New Issue
Block a user