Merge branch 'develop' into bugfix/members-info-panel

This commit is contained in:
Alexey Safronov 2023-09-29 17:08:28 +04:00
commit 6f1691f918
29 changed files with 10063 additions and 72 deletions

View File

@ -7,7 +7,7 @@ BUILD_PATH=${BUILD_PATH:-${SRC_PATH}/publish}
BUILD_DOTNET_CORE_ARGS=${BUILD_DOTNET_CORE_ARGS:-"false"}
PROPERTY_BUILD=${PROPERTY_BUILD:-"all"}
BACKEND_NODEJS_SERVICES=${BACKEND_NODEJS_SERVICES:-"ASC.Socket.IO, ASC.SsoAuth"}
BACKEND_NODEJS_SERVICES=${BACKEND_NODEJS_SERVICES:-"ASC.Socket.IO, ASC.SsoAuth, ASC.TelegramReports"}
BACKEND_DOTNETCORE_SERVICES=${BACKEND_DOTNETCORE_SERVICES:-"ASC.Files, ASC.People, ASC.Data.Backup, ASC.Files.Service, ASC.Notify, \
ASC.Studio.Notify, ASC.Web.Api, ASC.Web.Studio, ASC.Data.Backup.BackgroundTasks, ASC.ClearEvents, ASC.ApiSystem, ASC.Web.HealthChecks.UI"}
SELF_CONTAINED=${SELF_CONTAINED:-"false"}
@ -179,6 +179,12 @@ function backend-nodejs-publish {
yarn install --cwd ${SRC_PATH}/common/${ARRAY_NAME_SERVICES[$i]} --frozen-lockfile && \
mkdir -p ${BUILD_PATH}/services/${ARRAY_NAME_SERVICES[$i]}/service/ && \
cp -rfv ${SRC_PATH}/common/${ARRAY_NAME_SERVICES[$i]}/* ${BUILD_PATH}/services/${ARRAY_NAME_SERVICES[$i]}/service/
if [[ ${ARRAY_NAME_SERVICES[$i]} == "ASC.TelegramReports" ]]
then
# build before run
yarn --cwd ${BUILD_PATH}/services/ASC.TelegramReports/service/ build
fi
if [[ ${DOCKER_ENTRYPOINT} != "false" ]]
then
echo "== ADD ${DOCKER_ENTRYPOINT} to ${ARRAY_NAME_SERVICES[$i]} =="

View File

@ -101,6 +101,7 @@ done
services_name_backend_nodejs=()
services_name_backend_nodejs+=(ASC.Socket.IO)
services_name_backend_nodejs+=(ASC.SsoAuth)
services_name_backend_nodejs+=(ASC.TelegramReports)
# Publish backend services (Nodejs)
for i in ${!services_name_backend_nodejs[@]}; do

View File

@ -100,6 +100,7 @@
API_HOST=${CONTAINER_PREFIX}api
STUDIO_HOST=${CONTAINER_PREFIX}studio
SSOAUTH_HOST=${CONTAINER_PREFIX}ssoauth
TELEGRAMREPORTS_HOST=${CONTAINER_PREFIX}telegramreports
MIGRATION_RUNNER_HOST=${CONTAINER_PREFIX}migration-runner
PROXY_HOST=${CONTAINER_PREFIX}proxy
ROUTER_HOST=${CONTAINER_PREFIX}router
@ -122,6 +123,7 @@
SERVICE_API=${API_HOST}:${SERVICE_PORT}
SERVICE_STUDIO=${STUDIO_HOST}:${SERVICE_PORT}
SERVICE_SSOAUTH=${SSOAUTH_HOST}:${SERVICE_PORT}
SERVICE_TELEGRAMREPORTS=${TELEGRAMREPORTS_HOST}:${SERVICE_PORT}
SERVICE_DOCEDITOR=${DOCEDITOR_HOST}:5013
SERVICE_LOGIN=${LOGIN_HOST}:5011
SERVICE_HELTHCHECKS=${HELTHCHECKS_HOST}:${SERVICE_PORT}

View File

@ -1,8 +1,8 @@
version: "3.8"
services:
onlyoffice-dns:
dnsmasq:
image: jpillora/dnsmasq
container_name: onlyoffice-dns
container_name: dnsmasq
restart: always
expose:
- "5380"

View File

@ -267,6 +267,7 @@ services:
- SERVICE_API_SYSTEM=${SERVICE_API_SYSTEM}
- SERVICE_STUDIO=${SERVICE_STUDIO}
- SERVICE_SSOAUTH=${SERVICE_SSOAUTH}
- SERVICE_TELEGRAMREPORTS=${SERVICE_TELEGRAMREPORTS}
- SERVICE_DOCEDITOR=${SERVICE_DOCEDITOR}
- SERVICE_LOGIN=${SERVICE_LOGIN}
- SERVICE_HELTHCHECKS=${SERVICE_HELTHCHECKS}

View File

@ -53,10 +53,13 @@ public class DistributedTaskProgress : DistributedTask
[ProtoMember(3)]
protected int StepCount { get; set; }
protected CancellationToken CancellationToken { get; set; }
public virtual async Task RunJob(DistributedTask _, CancellationToken cancellationToken)
{
Percentage = 0;
Status = DistributedTaskStatus.Running;
CancellationToken = cancellationToken;
await DoJob();
}

View File

@ -47,8 +47,6 @@ public class ReassignProgressItem : DistributedTaskProgress
private bool _notify;
private bool _deleteProfile;
private CancellationToken _cancellationToken;
public ReassignProgressItem(IServiceScopeFactory serviceScopeFactory)
{
_serviceScopeFactory = serviceScopeFactory;
@ -70,13 +68,6 @@ public class ReassignProgressItem : DistributedTaskProgress
IsCompleted = false;
}
public override async Task RunJob(DistributedTask distributedTask, CancellationToken cancellationToken)
{
_cancellationToken = cancellationToken;
await base.RunJob(distributedTask, cancellationToken);
}
protected override async Task DoJob()
{
await using var scope = _serviceScopeFactory.CreateAsyncScope();
@ -167,7 +158,7 @@ public class ReassignProgressItem : DistributedTaskProgress
PublishChanges();
}
_cancellationToken.ThrowIfCancellationRequested();
CancellationToken.ThrowIfCancellationRequested();
}
private async Task SendSuccessNotifyAsync(UserManager userManager, StudioNotifyService studioNotifyService, MessageService messageService, MessageTarget messageTarget, DisplayUserSettingsHelper displayUserSettingsHelper)

View File

@ -0,0 +1,31 @@
module.exports = {
parser: "@typescript-eslint/parser",
parserOptions: {
project: "tsconfig.json",
tsconfigRootDir: __dirname,
sourceType: "module",
},
plugins: ["@typescript-eslint/eslint-plugin"],
extends: [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: [".eslintrc.js"],
"prettier/prettier": [
"error",
{
endOfLine: "auto",
},
],
rules: {
"@typescript-eslint/interface-name-prefix": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
},
};

35
common/ASC.TelegramReports/.gitignore vendored Executable file
View File

@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

View File

@ -0,0 +1,14 @@
# ASC Telegram Reports
ASC.TelegramReports
### Installation and usage
```bash
$ npm install
$ npm start
```
### License
[GNU GPL V3](LICENSE)

View File

@ -0,0 +1,7 @@
{
"app": {
"port": 5016,
"appsettings": "../../../../config",
"environment": "Development"
}
}

View File

@ -0,0 +1,26 @@
import * as nconf from "nconf";
import * as path from "path";
import * as fs from "fs";
import * as conf from "./config.json";
nconf.argv().env().file("config", path.join(__dirname, "config.json"));
getAndSaveAppsettings();
export default nconf;
function getAndSaveAppsettings() {
var appsettings = nconf.get("app").appsettings;
if (!path.isAbsolute(appsettings)) {
appsettings = path.join(__dirname, appsettings);
}
var env = nconf.get("app").environment;
console.log('environment: ' + env);
nconf.file("appsettingsWithEnv", path.join(appsettings, 'appsettings.' + env + '.json'));
nconf.file("appsettings", path.join(appsettings, 'appsettings.json'));
nconf.file("telegramConf", path.join(appsettings, "telegram.json"));
}

View File

@ -0,0 +1,31 @@
import firebase from "firebase/compat/app";
import "firebase/compat/database";
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./src/app/app.module";
import { AppService } from "./src/app/app.service";
import * as config from "./config";
const winston = require("./src/log.js");
const firebaseConfig = config.default.get("firebase");
firebase.initializeApp(firebaseConfig);
async function bootstrap() {
try {
const app = await NestFactory.create(AppModule);
const appService = app.get(AppService);
winston.info(`Start TelegramReports Service listening`);
const ref = firebase.database().ref("reports").limitToLast(1);
ref.on("child_added", (data) => {
appService.sendMessage(data.val())
});
} catch (e) {
winston.error(e);
}
}
bootstrap();

View File

@ -0,0 +1,9 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"entryFile": "index.js",
"compilerOptions": {
"deleteOutDir": true
}
}

View File

@ -0,0 +1,75 @@
{
"name": "telegram-reports",
"version": "1.0.0",
"description": "Server for telegram-reports",
"private": true,
"scripts": {
"prebuild": "rimraf dist",
"build": "rimraf dist & nest build",
"start": "rimraf dist & nest start",
"start:dev": "rimraf dist & nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/index",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix"
},
"dependencies": {
"@aws-sdk/client-cloudwatch-logs": "^3.199.0",
"@nestjs/common": "^9.0.0",
"@nestjs/core": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@nestjs/typeorm": "^9.0.0",
"date-and-time": "^2.4.1",
"firebase": "^10.4.0",
"mysql2": "^2.3.3",
"nconf": "^0.12.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"telegraf": "^4.13.1",
"typeorm": "^0.3.7",
"winston": "^3.8.2",
"winston-cloudwatch": "^6.1.1",
"winston-daily-rotate-file": "^4.5.5"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.4",
"@types/multer": "^1.4.7",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.1.2",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.5",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.0.0",
"typescript": "^4.3.5"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,9 @@
import { Module } from "@nestjs/common";
import { AppService } from "./app.service";
@Module({
imports: [],
controllers: [],
providers: [AppService],
})
export class AppModule { }

View File

@ -0,0 +1,43 @@
import { Injectable } from "@nestjs/common";
import { Telegraf } from "telegraf";
import * as config from "../../config";
const winston = require("../log.js");
const { botKey, chatId } = config.default.get("telegramConf");
const MAX_LENGTH = 4096; //TG Limit
@Injectable()
export class AppService {
bot = new Telegraf(botKey);
chunkMessage = (str: string, size: number): Array<string> =>
Array.from({ length: Math.ceil(str.length / size) }, (_, i) =>
str.slice(i * size, i * size + size),
);
async sendMessage(report): Promise<string> {
if (!botKey) throw new Error("Empty bot key");
if (!chatId) throw new Error("Empty chat ID");
const message = "New bug report:\n" + JSON.stringify(report);
try {
if (message.length > MAX_LENGTH) {
for (const part of this.chunkMessage(message, MAX_LENGTH)) {
await this.bot.telegram.sendMessage(chatId, part);
}
} else {
await this.bot.telegram.sendMessage(chatId, message);
}
winston.info(`Report sent successfully`, message);
return "Report sent successfully"
} catch (e) {
winston.error(e);
return e;
}
}
}

View File

@ -0,0 +1,103 @@
import * as winston from "winston";
import * as WinstonCloudWatch from "winston-cloudwatch";
import * as date from "date-and-time";
import * as os from "os";
import * as config from "../config";
import { randomUUID } from "crypto";
import "winston-daily-rotate-file";
import * as path from "path";
import * as fs from "fs";
let logpath = process.env.logpath || null;
if (logpath != null) {
if (!path.isAbsolute(logpath)) {
logpath = path.join(__dirname, "..", logpath);
}
}
const fileName = logpath
? path.join(logpath, "telegramreports.%DATE%.log")
: path.join(__dirname, "..", "..", "..", "..", "Logs", "telegramreports.%DATE%.log");
const dirName = path.dirname(fileName);
if (!fs.existsSync(dirName)) {
fs.mkdirSync(dirName);
}
const aws = config.default.get("aws").cloudWatch;
const accessKeyId = aws.accessKeyId;
const secretAccessKey = aws.secretAccessKey;
const awsRegion = aws.region;
const logGroupName = aws.logGroupName;
const logStreamName = aws.logStreamName.replace("${hostname}", os.hostname())
.replace("${applicationContext}", "TelegramReports")
.replace("${guid}", randomUUID())
.replace("${date}", date.format(new Date(), 'YYYY/MM/DDTHH.mm.ss'));
const options = {
file: {
filename: fileName,
datePattern: "MM-DD",
handleExceptions: true,
humanReadableUnhandledException: true,
zippedArchive: true,
maxSize: "50m",
maxFiles: "30d",
json: true,
},
console: {
level: "debug",
handleExceptions: true,
json: false,
colorize: true,
},
cloudWatch: {
name: 'aws',
level: "debug",
logStreamName: logStreamName,
logGroupName: logGroupName,
awsRegion: awsRegion,
jsonMessage: true,
awsOptions: {
credentials: {
accessKeyId: accessKeyId,
secretAccessKey: secretAccessKey
}
}
}
};
const transports: winston.transport[] = [
new winston.transports.Console(options.console),
new winston.transports.DailyRotateFile(options.file)
];
if (aws != null && aws.accessKeyId !== '') {
transports.push(new WinstonCloudWatch(options.cloudWatch));
}
const customFormat = winston.format(info => {
const now = new Date();
info.date = date.format(now, 'YYYY-MM-DD HH:mm:ss');
info.applicationContext = "TelegramReports";
info.level = info.level.toUpperCase();
const hostname = os.hostname();
info["instance-id"] = hostname;
return info;
})();
module.exports = winston.createLogger({
format: winston.format.combine(
customFormat,
winston.format.json()
),
transports: transports,
exitOnError: false,
});

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"resolveJsonModule": true
}
}

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,8 @@
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
using ASC.Core;
namespace ASC.ApiCache;
public class Startup
@ -117,6 +119,8 @@ public class Startup
{
app.UseCors(CustomCorsPolicyName);
}
app.UseSynchronizationContextMiddleware();
app.UseAuthentication();

View File

@ -139,6 +139,8 @@ public class Startup
{
app.UseCors(CustomCorsPolicyName);
}
app.UseSynchronizationContextMiddleware();
app.UseAuthentication();

View File

@ -259,7 +259,7 @@ server {
location ~* /plugins {
proxy_pass http://127.0.0.1:5014;
}
location ~* /migration {
proxy_pass http://127.0.0.1:5034;
}

6
config/telegram.json Normal file
View File

@ -0,0 +1,6 @@
{
"telegramConf": {
"botKey": "",
"chatId": ""
}
}

View File

@ -1,24 +1,30 @@
import IntegrationSvgUrl from "PUBLIC_DIR/images/integration.svg?url";
import IntegrationDarkSvgUrl from "PUBLIC_DIR/images/integration.dark.svg?url";
import React from "react";
import PropTypes from "prop-types";
import { withTranslation } from "react-i18next";
import { inject, observer } from "mobx-react";
import styled from "styled-components";
import { showLoader, hideLoader } from "@docspace/common/utils";
import Box from "@docspace/components/box";
import Text from "@docspace/components/text";
import Link from "@docspace/components/link";
import Badge from "@docspace/components/badge";
import toastr from "@docspace/components/toast/toastr";
import Button from "@docspace/components/button";
import { showLoader, hideLoader } from "@docspace/common/utils";
import ConsumerItem from "./sub-components/consumerItem";
import ConsumerModalDialog from "./sub-components/consumerModalDialog";
import { inject, observer } from "mobx-react";
import {
mobile,
smallTablet,
isSmallTablet,
} from "@docspace/components/utils/device";
import IntegrationSvgUrl from "PUBLIC_DIR/images/integration.svg?url";
import IntegrationDarkSvgUrl from "PUBLIC_DIR/images/integration.dark.svg?url";
import ConsumerItem from "./sub-components/consumerItem";
import ConsumerModalDialog from "./sub-components/consumerModalDialog";
import ThirdPartyLoader from "./sub-components/thirdPartyLoader";
const RootContainer = styled(Box)`
max-width: 700px;
@ -40,7 +46,6 @@ const RootContainer = styled(Box)`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(293px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.consumer-item-wrapper {
@ -66,10 +71,11 @@ const RootContainer = styled(Box)`
}
.business-plan {
grid-column: 1 / -1;
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 16px;
margin-bottom: -4px;
}
`;
@ -218,54 +224,56 @@ class ThirdPartyServices extends React.Component {
scale={isSmallTablet()}
/>
</Box>
<div className="consumers-list-container">
{freeConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
</div>
{!isThirdPartyAvailable && (
<div className="business-plan">
<Text fontSize="16px" fontWeight={700}>
{t("IncludedInBusiness")}
</Text>
<Badge
backgroundColor="#EDC409"
label={t("Common:Paid")}
isPaidBadge={true}
/>
{!consumers.length ? (
<ThirdPartyLoader />
) : (
<div className="consumers-list-container">
{freeConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
{!isThirdPartyAvailable && (
<div className="business-plan">
<Text fontSize="16px" fontWeight={700}>
{t("IncludedInBusiness")}
</Text>
<Badge
backgroundColor="#EDC409"
label={t("Common:Paid")}
isPaidBadge={true}
/>
</div>
)}
{paidConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
</div>
)}
<div className="consumers-list-container">
{paidConsumers.map((consumer) => (
<Box className="consumer-item-wrapper" key={consumer.name}>
<ConsumerItem
consumer={consumer}
dialogVisible={dialogVisible}
isLoading={isLoading}
onChangeLoading={onChangeLoading}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
setConsumer={setConsumer}
updateConsumerProps={updateConsumerProps}
t={t}
isThirdPartyAvailable={isThirdPartyAvailable}
/>
</Box>
))}
</div>
</RootContainer>
{dialogVisible && (
<ConsumerModalDialog

View File

@ -28,7 +28,7 @@ const StyledBox = styled(Box)`
css`
path {
fill: #ffffff;
opacity: ${props.isSet ? 1 : 0.16};
opacity: 1;
}
${props.isLinkedIn &&
css`
@ -45,7 +45,7 @@ const StyledBox = styled(Box)`
${(props) =>
!props.isThirdPartyAvailable &&
!props.isSet &&
props.canSet &&
css`
path {
opacity: 0.5;
@ -81,7 +81,7 @@ class ConsumerItem extends React.Component {
widthProp="100%"
>
<StyledBox
isSet={isSet}
canSet={consumer.canSet}
isLinkedIn={consumer.name === "linkedin"}
isThirdPartyAvailable={isThirdPartyAvailable}
>

View File

@ -0,0 +1,25 @@
import styled from "styled-components";
import Loaders from "@docspace/common/components/Loaders";
const StyledLoader = styled.div`
max-width: 700px;
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(293px, 1fr));
gap: 20px;
`;
const ThirdPartyLoader = () => {
const rectangles = new Array(6).fill(0);
return (
<StyledLoader>
{rectangles.map((_) => (
<Loaders.Rectangle height="120px" borderRadius="6px" />
))}
</StyledLoader>
);
};
export default ThirdPartyLoader;

View File

@ -214,7 +214,6 @@ abstract class FileOperation<T, TId> : FileOperation where T : FileOperationData
protected ILinkDao LinkDao { get; private set; }
protected IProviderDao ProviderDao { get; private set; }
protected ILogger Logger { get; private set; }
protected CancellationToken CancellationToken { get; private set; }
protected internal List<TId> Folders { get; private set; }
protected internal List<TId> Files { get; private set; }
protected ExternalShareData CurrentShareData { get; private set; }