diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml new file mode 100644 index 0000000000..42003aedfe --- /dev/null +++ b/.github/workflows/codeql.yaml @@ -0,0 +1,49 @@ +name: "CodeQL" + +on: + push: + branches: + - 'master' + - 'hotfix/**' + - 'release/**' + paths-ignore: + - '**/README.md' + - '**/LICENSE' + - '.github/**' + + schedule: + - cron: '0 0 * * 6' + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'javascript-typescript' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/damengDatabaseTests.yml b/.github/workflows/damengDatabaseTests.yml new file mode 100644 index 0000000000..028e974e5a --- /dev/null +++ b/.github/workflows/damengDatabaseTests.yml @@ -0,0 +1,51 @@ +name: Dameng database tests +on: + push: + branches: + - '**' + paths: + - 'tests/integration/databaseTests/**' + - 'DocService/sources/databaseConnectors/baseConnector.js' + - 'DocService/sources/databaseConnectors/damengConnector.js' +jobs: + dameng-tests: + name: Dameng + runs-on: ubuntu-latest + + steps: + - name: Run dameng DB docker container + run: docker run --name dameng -p 5236:5236 -e PAGE_SIZE=16 -e LD_LIBRARY_PATH=/opt/dmdbms/bin -e INSTANCE_NAME=dm8_01 -d danilaworker/damengdb:8.1.2 + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Caching dependencies + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: | + ./npm-shrinkwrap.json + ./Common/npm-shrinkwrap.json + ./DocService/npm-shrinkwrap.json + + - name: Install modules + run: | + npm ci + npm --prefix Common ci + npm --prefix DocService ci + + - name: Await database service to finish startup + run: sleep 15 + + - name: Creating service DB configuration + run: | + echo '{"services": {"CoAuthoring": {"sql": {"type": "dameng", "dbHost": "127.0.0.1", "dbPort": 5236, "dbUser": "SYSDBA", "dbPass": "SYSDBA001"}}}}' >> Common/config/local.json + + - name: Creating schema + run: | + docker cp ./schema/dameng/createdb.sql dameng:/ + docker exec dameng bash -c "cat /createdb.sql | /opt/dmdbms/bin/disql SYSDBA/SYSDBA001:5236" + + - name: Run Jest + run: npm run "integration database tests" diff --git a/.github/workflows/mssqlDatabaseTests.yml b/.github/workflows/mssqlDatabaseTests.yml new file mode 100644 index 0000000000..b29b33b1ea --- /dev/null +++ b/.github/workflows/mssqlDatabaseTests.yml @@ -0,0 +1,52 @@ +name: MSSQL database tests +on: + push: + branches: + - '**' + paths: + - 'tests/integration/databaseTests/**' + - 'DocService/sources/databaseConnectors/baseConnector.js' + - 'DocService/sources/databaseConnectors/mssqlConnector.js' +jobs: + mssql-tests: + name: MSSQL + runs-on: ubuntu-latest + + steps: + - name: Run MSSQL DB docker container + run: docker run --name mssql -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=onlYoff1ce" -p 8080:1433 -d mcr.microsoft.com/mssql/server:2022-latest + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Caching dependencies + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: | + ./npm-shrinkwrap.json + ./Common/npm-shrinkwrap.json + ./DocService/npm-shrinkwrap.json + + - name: Install modules + run: | + npm ci + npm --prefix Common ci + npm --prefix DocService ci + + - name: Creating service DB configuration + run: | + echo '{"services": {"CoAuthoring": {"sql": {"type": "mssql", "dbHost": "localhost", "dbPort": 8080, "dbUser": "sa", "dbPass": "onlYoff1ce"}}}}' >> Common/config/local.json + + - name: Await database service to finish startup + run: sleep 5 + + - name: Creating schema + run: | + docker cp ./schema/mssql/createdb.sql mssql:/ + docker exec mssql /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P onlYoff1ce -Q "CREATE DATABASE onlyoffice;" + docker exec mssql /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P onlYoff1ce -d onlyoffice -i /createdb.sql + + - name: Run Jest + run: npm run "integration database tests" diff --git a/.github/workflows/mysqlDatabaseTests.yml b/.github/workflows/mysqlDatabaseTests.yml new file mode 100644 index 0000000000..4edb8da52f --- /dev/null +++ b/.github/workflows/mysqlDatabaseTests.yml @@ -0,0 +1,48 @@ +name: MYSQL database tests +on: + push: + branches: + - '**' + paths: + - 'tests/integration/databaseTests/**' + - 'DocService/sources/databaseConnectors/baseConnector.js' + - 'DocService/sources/databaseConnectors/mysqlConnector.js' +jobs: + mysql-tests: + name: MYSQL + runs-on: ubuntu-latest + + steps: + - name: Run Mysql DB docker container + run: docker run --name mysql -p 8080:3306 -p 8081:33060 -e MYSQL_HOST=127.0.0.1 -e MYSQL_ROOT_PASSWORD=onlyoffice -e MYSQL_DATABASE=onlyoffice -d mysql:latest + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Caching dependencies + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: | + ./npm-shrinkwrap.json + ./Common/npm-shrinkwrap.json + ./DocService/npm-shrinkwrap.json + + - name: Install modules + run: | + npm ci + npm --prefix Common ci + npm --prefix DocService ci + + - name: Creating service DB configuration + run: | + echo '{"services": {"CoAuthoring": {"sql": {"type": "mysql", "dbHost": "127.0.0.1", "dbPort": "8080", "dbUser": "root", "dbPass": "onlyoffice"}}}}' >> Common/config/local.json + + - name : Creating schema + run: | + docker cp ./schema/mysql/createdb.sql mysql:/ + docker exec mysql mysql -h 127.0.0.1 -u root --password=onlyoffice -D onlyoffice -e 'source /createdb.sql' + + - name: Run Jest + run: npm run "integration database tests" diff --git a/.github/workflows/oracleDatabaseTests.yml b/.github/workflows/oracleDatabaseTests.yml new file mode 100644 index 0000000000..29e38deb47 --- /dev/null +++ b/.github/workflows/oracleDatabaseTests.yml @@ -0,0 +1,51 @@ +name: Oracle database tests +on: + push: + branches: + - '**' + paths: + - 'tests/integration/databaseTests/**' + - 'DocService/sources/databaseConnectors/baseConnector.js' + - 'DocService/sources/databaseConnectors/oracleConnector.js' +jobs: + oracle-tests: + name: Oracle + runs-on: ubuntu-latest + + steps: + - name: Run Oracle DB docker container + run: docker run --name oracle -p 8080:1521 -p 8081:5500 -e ORACLE_PASSWORD=admin -e APP_USER=onlyoffice -e APP_USER_PASSWORD=onlyoffice -d gvenzl/oracle-xe:21-slim + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Caching dependencies + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: | + ./npm-shrinkwrap.json + ./Common/npm-shrinkwrap.json + ./DocService/npm-shrinkwrap.json + + - name: Install modules + run: | + npm ci + npm --prefix Common ci + npm --prefix DocService ci + + - name: Creating service DB configuration + run: | + echo '{"services": {"CoAuthoring": {"sql": {"type": "oracle", "dbHost": "127.0.0.1", "dbPort": "8080", "dbUser": "onlyoffice", "dbPass": "onlyoffice", "dbName": "xepdb1"}}}}' >> Common/config/local.json + + - name: Await database service to finish startup + run: sleep 15 + + - name: Creating schema + run: | + docker cp ./schema/oracle/createdb.sql oracle:/ + docker exec oracle sqlplus -s onlyoffice/onlyoffice@//localhost/xepdb1 @/createdb.sql + + - name: Run Jest + run: npm run "integration database tests" diff --git a/.github/workflows/postgreDatabaseTests.yml b/.github/workflows/postgreDatabaseTests.yml new file mode 100644 index 0000000000..f02b68b743 --- /dev/null +++ b/.github/workflows/postgreDatabaseTests.yml @@ -0,0 +1,48 @@ +name: Postgre database tests +on: + push: + branches: + - '**' + paths: + - 'tests/integration/databaseTests/**' + - 'DocService/sources/databaseConnectors/baseConnector.js' + - 'DocService/sources/databaseConnectors/postgreConnector.js' +jobs: + postgres-tests: + name: Postgres + runs-on: ubuntu-latest + + steps: + - name: Run Postgres DB docker container + run: docker run --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=onlyoffice -e POSTGRES_USER=onlyoffice -e POSTGRES_DB=onlyoffice -d postgres:latest + + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Caching dependencies + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: | + ./npm-shrinkwrap.json + ./Common/npm-shrinkwrap.json + ./DocService/npm-shrinkwrap.json + + - name: Install modules + run: | + npm ci + npm --prefix Common ci + npm --prefix DocService ci + + - name: Creating service DB configuration + run: | + echo '{"services": {"CoAuthoring": {"sql": {"dbHost": "127.0.0.1"}}}}' >> Common/config/local.json + + - name: Creating schema + run: | + docker cp ./schema/postgresql/createdb.sql postgres:/ + docker exec postgres psql -d onlyoffice -U onlyoffice -a -f /createdb.sql + + - name: Run Jest + run: npm run "integration database tests" diff --git a/.github/workflows/unitTests.yml b/.github/workflows/unitTests.yml new file mode 100644 index 0000000000..43be2cb8f7 --- /dev/null +++ b/.github/workflows/unitTests.yml @@ -0,0 +1,36 @@ +name: Service unit tests +on: + push: + branches: + - '**' + paths: + - '**.js' + - '!tests/integration/**' + - '!DocService/sources/databaseConnectors/**' +jobs: + unit-tests: + name: Service unit tests + runs-on: ubuntu-latest + + steps: + - name: Check out repository code + uses: actions/checkout@v3 + + - name: Caching dependencies + uses: actions/setup-node@v3 + with: + node-version: '16' + cache: 'npm' + cache-dependency-path: | + ./npm-shrinkwrap.json + ./Common/npm-shrinkwrap.json + ./DocService/npm-shrinkwrap.json + + - name: Install modules + run: | + npm ci + npm --prefix Common ci + npm --prefix DocService ci + + - name: Run Jest + run: npm run "unit tests" diff --git a/.gitignore b/.gitignore index c11eda6902..e7a33fb04f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,14 +3,11 @@ Common/config/local.json license.lic App_Data/ */node_modules -FileConverter/bin/*.dll -FileConverter/bin/*.dylib -FileConverter/bin/*.exe -FileConverter/bin/x2t -FileConverter/bin/*.S -FileConverter/bin/font_selection.bin -FileConverter/bin/HtmlFileInternal +FileConverter/bin DocService/npm-debug.log build node_modules -SpellChecker/dictionaries +/Gruntfile.js.out +local-development-*.json +*.pyc +run-develop-local.py \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 4af5e0e479..cad6c3f96f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,10 @@ +dist: trusty language: node_js node_js: - - "6" - - "4" \ No newline at end of file + - '6' +before_install: npm install -g grunt-cli +script: + - make +matrix: + allow_failures: + - node_js: '6' \ No newline at end of file diff --git a/3DPARTY.md b/3DPARTY.md new file mode 100644 index 0000000000..e6806317de --- /dev/null +++ b/3DPARTY.md @@ -0,0 +1,68 @@ + +## Third-party + +- @aws-sdk/client-s3 3.637.0 ([Apache-2.0](https://raw.githubusercontent.com/aws/aws-sdk-js-v3/main/LICENSE)) +- @aws-sdk/node-http-handler 3.374.0 ([Apache-2.0](https://raw.githubusercontent.com/aws/aws-sdk-js-v3/main/LICENSE)) +- @aws-sdk/s3-request-presigner 3.370.0 ([Apache-2.0](https://raw.githubusercontent.com/aws/aws-sdk-js-v3/main/LICENSE)) +- amqplib 0.8.0 ([MIT](https://raw.githubusercontent.com/amqp-node/amqplib/main/LICENSE)) +- co 4.6.0 ([MIT](https://raw.githubusercontent.com/tj/co/master/LICENSE)) +- config 2.0.1 ([MIT](https://raw.githubusercontent.com/node-config/node-config/master/LICENSE)) +- content-disposition 0.5.3 ([MIT](https://raw.githubusercontent.com/jshttp/content-disposition/master/LICENSE)) +- dnscache 1.0.1 ([BSD](https://raw.githubusercontent.com/yahoo/dnscache/master/LICENSE)) +- escape-string-regexp 1.0.5 ([MIT](https://raw.githubusercontent.com/sindresorhus/escape-string-regexp/main/license)) +- forwarded 0.1.2 ([MIT](https://raw.githubusercontent.com/jshttp/forwarded/master/LICENSE)) +- ipaddr.js 1.8.1 ([MIT](https://raw.githubusercontent.com/whitequark/ipaddr.js/main/LICENSE)) +- jsonwebtoken 9.0.0 ([MIT](https://raw.githubusercontent.com/auth0/node-jsonwebtoken/master/LICENSE)) +- log4js 6.4.1 ([Apache-2.0](https://raw.githubusercontent.com/log4js-node/log4js-node/master/LICENSE)) +- mime 2.3.1 ([MIT](https://raw.githubusercontent.com/broofa/mime/main/LICENSE)) +- ms 2.1.1 ([MIT](https://raw.githubusercontent.com/vercel/ms/main/license.md)) +- node-cache 4.2.1 ([MIT](https://raw.githubusercontent.com/node-cache/node-cache/master/LICENSE)) +- node-statsd 0.1.1 ([MIT](https://raw.githubusercontent.com/sivy/node-statsd/master/LICENSE)) +- nodemailer 6.9.13 ([MIT-0](https://raw.githubusercontent.com/nodemailer/nodemailer/master/LICENSE)) +- request 2.88.0 ([Apache-2.0](https://raw.githubusercontent.com/request/request/master/LICENSE)) +- request-filtering-agent 1.0.5 ([MIT](https://raw.githubusercontent.com/azu/request-filtering-agent/master/LICENSE)) +- rhea 1.0.24 ([Apache-2.0](https://raw.githubusercontent.com/amqp/rhea/main/LICENSE)) +- uri-js 4.2.2 ([BSD-2-Clause](https://raw.githubusercontent.com/garycourt/uri-js/master/LICENSE)) +- win-ca 3.5.0 ([MIT](https://raw.githubusercontent.com/ukoloff/win-ca/master/LICENSE)) +- ajv 8.9.0 ([MIT](https://raw.githubusercontent.com/ajv-validator/ajv/master/LICENSE)) +- apicache 1.6.3 ([MIT](https://raw.githubusercontent.com/kwhitley/apicache/master/LICENSE)) +- base64-stream 1.0.0 ([MIT](https://github.com/mazira/base64-stream?tab=readme-ov-file#license)) +- body-parser 1.20.1 ([MIT](https://raw.githubusercontent.com/expressjs/body-parser/master/LICENSE)) +- bottleneck 2.19.5 ([MIT](https://raw.githubusercontent.com/SGrondin/bottleneck/master/LICENSE)) +- bytes 3.0.0 ([MIT](https://raw.githubusercontent.com/visionmedia/bytes.js/master/LICENSE)) +- co 4.6.0 ([MIT](https://raw.githubusercontent.com/tj/co/master/LICENSE)) +- config 2.0.1 ([MIT](https://raw.githubusercontent.com/node-config/node-config/master/LICENSE)) +- cron 1.5.0 ([MIT](https://raw.githubusercontent.com/kelektiv/node-cron/main/LICENSE)) +- deep-equal 1.0.1 ([MIT](https://raw.githubusercontent.com/inspect-js/node-deep-equal/main/LICENSE)) +- dmdb 1.0.14280 ([none](https://www.npmjs.com/package/dmdb)) +- ejs 3.1.10 ([Apache-2.0](https://raw.githubusercontent.com/mde/ejs/main/LICENSE)) +- exif-parser 0.1.12 ([MIT](https://raw.githubusercontent.com/bwindels/exif-parser/master/LICENSE.md)) +- express 4.19.2 ([MIT](https://raw.githubusercontent.com/expressjs/express/master/LICENSE)) +- fakeredis 2.0.0 ([MIT](https://github.com/hdachev/fakeredis?tab=readme-ov-file#license)) +- ioredis 5.3.1 ([MIT](https://raw.githubusercontent.com/redis/ioredis/main/LICENSE)) +- jimp 0.22.10 ([MIT](https://raw.githubusercontent.com/jimp-dev/jimp/main/LICENSE)) +- jsonwebtoken 9.0.0 ([MIT](https://raw.githubusercontent.com/auth0/node-jsonwebtoken/master/LICENSE)) +- jwa 1.1.6 ([MIT](https://raw.githubusercontent.com/auth0/node-jwa/master/LICENSE)) +- mime 2.3.1 ([MIT](https://raw.githubusercontent.com/broofa/mime/main/LICENSE)) +- mime-db 1.49.0 ([MIT](https://raw.githubusercontent.com/jshttp/mime-db/master/LICENSE)) +- ms 2.1.1 ([MIT](https://raw.githubusercontent.com/vercel/ms/master/license.md)) +- mssql 9.1.1 ([MIT](https://raw.githubusercontent.com/tediousjs/node-mssql/master/LICENSE.md)) +- multer 1.4.3 ([MIT](https://raw.githubusercontent.com/expressjs/multer/master/LICENSE)) +- multi-integer-range 4.0.7 ([MIT](https://raw.githubusercontent.com/smikitky/node-multi-integer-range/master/LICENSE)) +- multiparty 4.2.1 ([MIT](https://raw.githubusercontent.com/pillarjs/multiparty/master/LICENSE)) +- mysql2 3.9.8 ([MIT](https://raw.githubusercontent.com/sidorares/node-mysql2/master/License)) +- oracledb 6.3.0 ([(Apache-2.0 OR UPL-1.0)](https://raw.githubusercontent.com/oracle/node-oracledb/main/LICENSE.txt)) +- pg 8.11.3 ([MIT](https://raw.githubusercontent.com/brianc/node-postgres/master/LICENSE)) +- redis 4.6.11 ([MIT](https://raw.githubusercontent.com/redis/node-redis/master/LICENSE)) +- retry 0.12.0 ([MIT](https://raw.githubusercontent.com/tim-kos/node-retry/master/License)) +- socket.io 4.8.1 ([MIT](https://raw.githubusercontent.com/socketio/socket.io/main/LICENSE)) +- underscore 1.13.1 ([MIT](https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE)) +- utf7 1.0.2 ([BSD](https://www.npmjs.com/package/utf7)) +- windows-locale 1.0.1 ([MIT](https://raw.githubusercontent.com/TiagoDanin/Windows-Locale/master/LICENSE)) +- xmlbuilder2 3.0.2 ([MIT](https://raw.githubusercontent.com/oozcitak/xmlbuilder2/master/LICENSE)) +- @expo/spawn-async 1.7.2 ([MIT](https://raw.githubusercontent.com/TritonDataCenter/node-spawn-async/master/LICENSE)) +- bytes 3.0.0 ([MIT](https://raw.githubusercontent.com/visionmedia/bytes.js/master/LICENSE)) +- co 4.6.0 ([MIT](https://raw.githubusercontent.com/tj/co/master/LICENSE)) +- config 2.0.1 ([MIT](https://github.com/node-config/node-config/blob/master/LICENSE)) +- lcid 3.1.1 ([MIT](https://raw.githubusercontent.com/sindresorhus/lcid/main/license)) +- statsd 0.8.4 ([MIT](https://raw.githubusercontent.com/statsd/statsd/master/LICENSE)) diff --git a/3d-party-lic-report/json2md.js b/3d-party-lic-report/json2md.js new file mode 100644 index 0000000000..dc0c503c02 --- /dev/null +++ b/3d-party-lic-report/json2md.js @@ -0,0 +1,73 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; +const { readFile, writeFile } = require("node:fs/promises"); + +async function startTest() { + let args = process.argv.slice(2); + if (args.length < 1) { + console.error('missing arguments.USAGE: json2md.js [output.md] [input.json]'); + return; + } + console.info("3d license report start"); + let outputMd = ''; + let outputFlag = 'a'; + let outputPath = args[0]; + let inputPath = args[1]; + + if (inputPath) { + let licensesText = await readFile(inputPath, 'utf-8'); + let licensesJson = JSON.parse(licensesText); + console.info("3d license report license count: %d", licensesJson.length); + + for (const element of licensesJson) { + let name = element['name']; + let installedVersion = element['installedVersion']; + let licenseType = element['licenseType']; + let licenseFileLink = element['licenseFileLink']; + outputMd += `- ${name} ${installedVersion} ([${licenseType}](${licenseFileLink}))\n` + } + } else { + outputMd = '\n## Third-party\n\n'; + outputFlag = 'w'; + } + + await writeFile(outputPath, outputMd, {flag: outputFlag}, 'utf-8'); + console.info("3d license report end"); +} + +startTest().catch((err) => { + console.error(err.stack); +}).finally(() => { + process.exit(0); +}); \ No newline at end of file diff --git a/3d-party-lic-report/license-report-config.json b/3d-party-lic-report/license-report-config.json new file mode 100644 index 0000000000..b42b847e8b --- /dev/null +++ b/3d-party-lic-report/license-report-config.json @@ -0,0 +1,8 @@ +{ + "fields": [ + "name", + "licenseType", + "link", + "installedVersion" + ] +} \ No newline at end of file diff --git a/3rd-Party.txt b/3rd-Party.txt index cda89f16ea..63b096feaf 100644 --- a/3rd-Party.txt +++ b/3rd-Party.txt @@ -28,105 +28,84 @@ License: MIT License License File: license/Megapixel.license -5. SockJS - WebSocket emulation - Javascript client +5. SocketIO - WebSocket emulation - Javascript client -URL: http://sockjs.org +URL: https://socket.io License: MIT License -License File: license/SockJS.license +License File: license/SocketIO.license -6. Sencha Touch - a high-performance HTML5 mobile application framework, is the cornerstone of the Sencha HTML5 platform. Built for enabling world-class user experiences, Sencha Touch is the only framework that enables developers to build powerful apps that work on iOS, Android, BlackBerry, Windows Phone, and more. - -URL: http://www.sencha.com/products/touch -License: GPLv3 -License File: license/Touch.license - - -7. Underscore - Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects. It's the tie to go along with jQuery's tux, and Backbone.js's suspenders. +6. Underscore - Underscore is a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects. It's the tie to go along with jQuery's tux, and Backbone.js's suspenders. URL: https://github.com/jashkenas/underscore/ License: MIT License License File: license/Underscore.license -8. XRegExp - XRegExp is an open source (MIT License) JavaScript library that provides augmented and extensible regular expressions. You get new syntax, flags, and methods beyond what browsers support natively. XRegExp is also a regex utility belt with tools to make your client-side grepping simpler and more powerful, while freeing you from worrying about pesky cross-browser inconsistencies and the dubious lastIndex property. +7. XRegExp - XRegExp is an open source (MIT License) JavaScript library that provides augmented and extensible regular expressions. You get new syntax, flags, and methods beyond what browsers support natively. XRegExp is also a regex utility belt with tools to make your client-side grepping simpler and more powerful, while freeing you from worrying about pesky cross-browser inconsistencies and the dubious lastIndex property. URL: http://xregexp.com/ License: MIT License License File: license/XRegExp.license -9. ZeroClipboard - The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. +8. ZeroClipboard - The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. URL: http://zeroclipboard.org License: MIT License License File: license/ZeroClipboard.license -10. Hunspell - Hunspell is the spell checker of LibreOffice, OpenOffice.org, Mozilla Firefox 3 & Thunderbird, Google Chrome, and it is also used by proprietary software packages, like Mac OS X, InDesign, memoQ, Opera and SDL Trados. +9. Hunspell - Hunspell is the spell checker of LibreOffice, OpenOffice.org, Mozilla Firefox 3 & Thunderbird, Google Chrome, and it is also used by proprietary software packages, like Mac OS X, InDesign, memoQ, Opera and SDL Trados. URL: http://sourceforge.net/projects/hunspell/ License: MPL 1.1/GPL 2.0/LGPL 2.1 License File: license/Hunspell.license -11. NodeHun- The Hunspell binding for nodejs that exposes as much of hunspell as possible and also adds new features. +10. NodeHun - The Hunspell binding for nodejs that exposes as much of hunspell as possible and also adds new features. URL: https://npmjs.org/package/nodehun License: MIT License License File: license/NodeHun.license -12. Backbone - Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. +11. Backbone - Backbone.js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface. URL: http://backbonejs.org/ License: MIT License License File: license/Backbone.license -13. jQuery.browser - A jQuery plugin for browser detection. +12. jQuery.browser - A jQuery plugin for browser detection. URL: http://api.jquery.com/jquery.browser/ License: MIT License License File: license/jQuery.browser.license -14. PerfectScrollbar - Tiny but perfect jQuery scrollbar plugin. +13. PerfectScrollbar - Tiny but perfect jQuery scrollbar plugin. URL: http://noraesae.github.com/perfect-scrollbar/ License: MIT License License File: license/PerfectScrollbar.license -15. jsrsasign - The 'jsrsasign' (RSA-Sign JavaScript Library) is a open source free pure JavaScript implementation of PKCS#1 v2.1 RSASSA-PKCS1-v1_5 RSA signing and validation algorithm. +14. jsrsasign - The 'jsrsasign' (RSA-Sign JavaScript Library) is a open source free pure JavaScript implementation of PKCS#1 v2.1 RSASSA-PKCS1-v1_5 RSA signing and validation algorithm. URL: http://kjur.github.io/jsrsasign/ License: MIT License License File: license/jsrsasign.license -16. JSZip - SZip is a javascript library for creating, reading and editing .zip files, with a lovely and simple API. - -URL: http://stuk.github.io/jszip/ -License: MIT license or the GPLv3 license -License File: license/jszip.license - - -17. JSZipUtils - A collection of cross-browser utilities to go along with JSZip. - -URL: http://stuk.github.io/jszip-utils/ -License: MIT license or the GPLv3 license -License File: license/jszip.license - - -18. less - Less is a CSS pre-processor +15. less - Less is a CSS pre-processor URL: http://lesscss.org/ License: Apache 2 License License File: license/less.license -19. requirejs-text - A RequireJS/AMD loader plugin for loading text resources +16. requirejs-text - A RequireJS/AMD loader plugin for loading text resources URL: http://lesscss.org/ License: MIT License diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..bfc83c5930 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Change log +## develop +### Back-end +* + +## 5.1.1 +### Back-end +* Add reconnection.attempts, reconnection.delay options to config - applicable for editor-server connection +* Add sockjs config section for testing purposes +* Fix inconsistent database status after files assemble in case of rapid open/close connection diff --git a/Common/config/default.json b/Common/config/default.json index 41e1f80286..1a24df1f6e 100644 --- a/Common/config/default.json +++ b/Common/config/default.json @@ -7,55 +7,321 @@ }, "log": { "filePath": "", - "options": { "reloadSecs": 60 } + "options": { + "replaceConsole": true + } }, "queue": { + "type": "rabbitmq", "visibilityTimeout": 300, "retentionPeriod": 900 }, + "email": { + "smtpServerConfiguration": { + "host": "localhost", + "port": 587, + "auth": { + "user": "", + "pass": "" + } + }, + "connectionConfiguration": { + "disableFileAccess": false, + "disableUrlAccess": false + }, + "contactDefaults": { + "from": "from@example.com", + "to": "to@example.com" + } + }, + "notification": { + "rules": { + "licenseExpirationWarning": { + "enable": false, + "transportType": [ + "email" + ], + "template": { + "title": "%s Docs license expiration warning", + "body": "Attention! Your license is about to expire on %s.\nUpon reaching this date, you will no longer be entitled to receive personal technical support and install new Docs versions released after this date." + }, + "policies": { + "repeatInterval": "1d" + } + }, + "licenseExpirationError": { + "enable": false, + "transportType": [ + "email" + ], + "template": { + "title": "%s Docs license expiration warning", + "body": "Attention! Your license expired on %s.\nYou are no longer entitled to receive personal technical support and install new Docs versions released after this date.\nPlease contact sales@onlyoffice.com to discuss license renewal." + }, + "policies": { + "repeatInterval": "1d" + } + }, + "licenseLimitEdit": { + "enable": false, + "transportType": [ + "email" + ], + "template": { + "title": "%s Docs license connection limit warning", + "body": "Attention! You have reached %s%% of the %s limit set by your license." + }, + "policies": { + "repeatInterval": "1h" + } + }, + "licenseLimitLiveViewer": { + "enable": false, + "transportType": [ + "email" + ], + "template": { + "title": "%s Docs license connection limit warning", + "body": "Attention! You have reached %s%% of the live viewer %s limit set by your license." + }, + "policies": { + "repeatInterval": "1h" + } + } + } + }, "storage": { - "name": "storage-s3", + "name": "storage-fs", + "fs": { + "folderPath": "", + "urlExpires": 900, + "secretString": "verysecretstring" + }, "region": "", "endpoint": "http://localhost/s3", "bucketName": "cache", "storageFolderName": "files", + "cacheFolderName": "data", "urlExpires": 604800, "accessKeyId": "AKID", "secretAccessKey": "SECRET", - "useRequestToGetUrl": false, - "useSignedUrl": false, + "sslEnabled": false, + "s3ForcePathStyle": true, "externalHost": "" }, + "persistentStorage": { + }, "rabbitmq": { - "url": "amqp://guest:guest@localhost:5672", + "url": "amqp://localhost:5672", "socketOptions": {}, - "exchangepubsub": "ds.pubsub", + "exchangepubsub": { + "name": "ds.pubsub", + "options": { + "durable": true + } + }, + "queuepubsub": { + "name": "", + "options": { + "autoDelete": true, + "exclusive": true, + "arguments": { + "x-queue-type": "classic" + } + } + }, + "queueconverttask": { + "name": "ds.converttask6", + "options": { + "durable": true, + "maxPriority": 6, + "arguments": { + "x-queue-type": "classic" + } + } + }, + "queueconvertresponse": { + "name": "ds.convertresponse", + "options": { + "durable": true, + "arguments": { + "x-queue-type": "classic" + } + } + }, + "exchangeconvertdead": { + "name": "ds.exchangeconvertdead", + "options": { + "durable": true + } + }, + "queueconvertdead": { + "name": "ds.convertdead", + "options": { + "durable": true, + "arguments": { + "x-queue-type": "classic" + } + } + }, + "queuedelayed": { + "name": "ds.delayed", + "options": { + "durable": true, + "arguments": { + "x-queue-type": "classic" + } + } + } + }, + "activemq": { + "connectOptions": { + "port": 5672, + "host": "localhost", + "reconnect": false + }, "queueconverttask": "ds.converttask", "queueconvertresponse": "ds.convertresponse", - "exchangeconvertdead": "ds.exchangeconvertdead", - "queueconvertdead": "ds.convertdead" + "queueconvertdead": "ActiveMQ.DLQ", + "queuedelayed": "ds.delayed", + "topicpubsub": "ds.pubsub" }, "dnscache": { "enable" : true, "ttl" : 300, "cachesize" : 1000 }, + "openpgpjs": { + "config": { + }, + "encrypt": { + "passwords": ["verysecretstring"] + }, + "decrypt": { + "passwords": ["verysecretstring"] + } + }, + "aesEncrypt": { + "config": { + "keyByteLength": 32, + "saltByteLength": 64, + "initializationVectorByteLength": 16, + "iterationsByteLength": 5 + }, + "secret": "verysecretstring" + }, + "bottleneck": { + "getChanges": { + } + }, + "win-ca": { + "inject": "+" + }, + "wopi": { + "enable": false, + "host" : "", + "htmlTemplate" : "../../web-apps/apps/api/wopi", + "wopiZone" : "external-http", + "favIconUrlWord" : "/web-apps/apps/documenteditor/main/resources/img/favicon.ico", + "favIconUrlCell" : "/web-apps/apps/spreadsheeteditor/main/resources/img/favicon.ico", + "favIconUrlSlide" : "/web-apps/apps/presentationeditor/main/resources/img/favicon.ico", + "favIconUrlPdf" : "/web-apps/apps/pdfeditor/main/resources/img/favicon.ico", + "favIconUrlDiagram" : "/web-apps/apps/visioeditor/main/resources/img/favicon.ico", + "fileInfoBlockList" : ["FileUrl"], + "pdfView": ["djvu", "xps", "oxps"], + "pdfEdit": ["pdf"], + "forms": ["pdf"], + "wordView": ["doc", "dotm", "dot", "fodt", "ott", "rtf", "mht", "mhtml", "html", "htm", "xml", "epub", "fb2", "sxw", "stw", "wps", "wpt", "pages", "docxf", "oform"], + "wordEdit": ["docx", "dotx", "docm", "odt", "txt"], + "cellView": ["xls", "xlsb", "xltm", "xlt", "fods", "ots", "sxc", "xml", "et", "ett", "numbers"], + "cellEdit": ["xlsx", "xltx", "xlsm", "ods", "csv"], + "slideView": ["ppt", "ppsx", "ppsm", "pps", "potm", "pot", "fodp", "otp", "sxi", "dps", "dpt", "key"], + "slideEdit": ["pptx", "potx", "pptm", "odp"], + "diagramView": [], + "diagramEdit": [], + "publicKey": "", + "modulus": "", + "exponent": 65537, + "privateKey": "", + "publicKeyOld": "", + "modulusOld": "", + "exponentOld": 65537, + "privateKeyOld": "", + "refreshLockInterval": "10m", + "dummy" : { + "enable": false, + "sampleFilePath": "" + } + }, + "tenants": { + "baseDir": "", + "baseDomain": "", + "filenameConfig": "config.json", + "filenameSecret": "secret.key", + "filenameLicense": "license.lic", + "defaultTenant": "localhost", + "cache" : { + "stdTTL": 300, + "checkperiod": 60, + "useClones": false + } + }, + "externalRequest": { + "directIfIn" : { + "allowList": [], + "jwtToken": true + }, + "action": { + "allow": true, + "blockPrivateIP": true, + "proxyUrl": "", + "proxyUser": { + "username": "", + "password": "" + }, + "proxyHeaders": { + } + } + }, "services": { "CoAuthoring": { "server": { "port": 8000, "workerpercpu": 1, "mode": "development", - "fonts_route": "fonts/", - "sockjsUrl": "", "limits_tempfile_upload": 104857600, "limits_image_size": 26214400, - "limits_image_download_timeout": 120, - "editor_settings_spellchecker_url": "/spellchecker", - "callbackRequestTimeout": 120, + "limits_image_download_timeout": { + "connectionAndInactivity": "2m", + "wholeCycle": "2m" + }, + "callbackRequestTimeout": { + "connectionAndInactivity": "10m", + "wholeCycle": "10m" + }, "healthcheckfilepath": "../public/healthcheck.docx", "savetimeoutdelay": 5000, - "edit_singleton": false + "edit_singleton": false, + "forgottenfiles": "forgotten", + "forgottenfilesname": "output", + "maxRequestChanges": 20000, + "openProtectedFile": true, + "isAnonymousSupport": true, + "editorDataStorage": "editorDataMemory", + "editorStatStorage": "", + "assemblyFormatAsOrigin": true, + "newFileTemplate" : "../../document-templates/new", + "downloadFileAllowExt": ["pdf", "xlsx"], + "tokenRequiredParams": true, + "forceSaveUsingButtonWithoutChanges": false + }, + "requestDefaults": { + "headers": { + "User-Agent": "Node.js/6.13", + "Connection": "Keep-Alive" + }, + "gzip": true, + "rejectUnauthorized": true }, "autoAssembly": { "enable": false, @@ -65,9 +331,7 @@ "utils": { "utils_common_fontdir": "null", "utils_fonts_search_patterns": "*.ttf;*.ttc;*.otf", - "resource_expires": 31536000, - "limits_image_types_upload": "jpg;png;gif;bmp", - "limits_image_types_copy": "jpg;png;gif;bmp;emf;wmf;svg;txt;bin" + "limits_image_types_upload": "jpg;jpeg;jpe;png;gif;bmp;svg;tiff;tif" }, "sql": { "type": "postgres", @@ -80,16 +344,58 @@ "dbPass": "onlyoffice", "charset": "utf8", "connectionlimit": 10, - "max_allowed_packet": 1048575 + "max_allowed_packet": 1048575, + "pgPoolExtraOptions": { + "idleTimeoutMillis": 30000, + "maxLifetimeSeconds ": 60000, + "statement_timeout ": 60000, + "query_timeout ": 60000, + "connectionTimeoutMillis": 60000 + }, + "damengExtraOptions": { + "columnNameUpperCase": false, + "columnNameCase": "lower", + "connectTimeout": 60000, + "loginEncrypt": false, + "localTimezone": 0, + "poolTimeout": 60, + "socketTimeout": 60000, + "queueTimeout": 60000 + }, + "oracleExtraOptions": { + "connectTimeout": 60 + }, + "msSqlExtraOptions": { + "options": { + "encrypt": false, + "trustServerCertificate": true + }, + "pool": { + "idleTimeoutMillis": 30000 + } + }, + "mysqlExtraOptions": { + "connectTimeout": 60000, + "queryTimeout": 60000 + } }, "redis": { "name": "redis", "prefix": "ds:", - "host": "localhost", - "port": 6379 + "host": "127.0.0.1", + "port": 6379, + "options": {}, + "optionsCluster": {}, + "iooptions": { + "lazyConnect": true + }, + "iooptionsClusterNodes": [ + ], + "iooptionsClusterOptions": { + "lazyConnect": true + } }, "pubsub": { - "name": "pubsubRabbitMQ", "maxChanges": 1000 }, "expire": { @@ -97,28 +403,36 @@ "presence": 300, "locks": 604800, "changeindex": 86400, - "lockDoc": 60, + "lockDoc": 30, "message": 86400, "lastsave": 604800, "forcesave": 604800, + "forcesaveLock": 5000, "saved": 3600, "documentsCron": "0 */2 * * * *", "files": 86400, "filesCron": "00 00 */1 * * *", "filesremovedatonce": 100, - "sessionidle": "0", - "sessionabsolute": "0", + "sessionidle": "1h", + "sessionabsolute": "30d", "sessionclosecommand": "2m", "pemStdTTL": "1h", - "pemCheckPeriod": "10m" + "pemCheckPeriod": "10m", + "updateVersionStatus": "5m", + "monthUniqueUsers": "1y" }, "ipfilter": { "rules": [{"address": "*", "allowed": true}], "useforrequest": false, - "errorcode": 401 + "errorcode": 403 + }, + "request-filtering-agent" : { + "allowPrivateIPAddress": false, + "allowMetaIPAddress": false }, "secret": { - "inbox": {"string": "secret", "file": "", "tenants": {}}, + "browser": {"string": "secret", "file": ""}, + "inbox": {"string": "secret", "file": ""}, "outbox": {"string": "secret", "file": ""}, "session": {"string": "secret", "file": ""} }, @@ -130,100 +444,121 @@ "outbox": false } }, + "browser": { + "secretFromInbox": true + }, "inbox": { "header": "Authorization", - "prefix": "Bearer " + "prefix": "Bearer ", + "inBody": false }, "outbox": { "header": "Authorization", "prefix": "Bearer ", "algorithm": "HS256", - "expires": "5m" + "expires": "5m", + "inBody": false, + "urlExclusionRegex": "" }, "session": { "algorithm": "HS256", - "expires": "10m" + "expires": "30d" + }, + "verifyOptions": { + "clockTolerance": 60 } }, "plugins": { - "uri": "/sdkjs-plugins" + "uri": "/sdkjs-plugins", + "autostart": [] + }, + "themes": { + "uri": "/web-apps/apps/common/main/resources/themes" + }, + "editor":{ + "spellcheckerUrl": "", + "reconnection":{ + "attempts": 50, + "delay": "2s" + }, + "binaryChanges": false, + "websocketMaxPayloadSize": "1.5MB", + "maxChangesSize": "150MB" + }, + "sockjs": { + "sockjs_url": "", + "disable_cors": true, + "websocket": true + }, + "socketio": { + "connection": { + "path": "/doc/", + "serveClient": false, + "pingTimeout": 20000, + "pingInterval": 25000, + "maxHttpBufferSize": 1e8 + } + }, + "callbackBackoffOptions": { + "retries": 0, + "timeout":{ + "factor": 2, + "minTimeout": 1000, + "maxTimeout": 2147483647, + "randomize": false + }, + "httpStatus": "429,500-599" } } }, "license" : { - "license_file": "" + "license_file": "", + "warning_limit_percents": 70, + "packageType": 0, + "warning_license_expiration": "30d" }, "FileConverter": { "converter": { - "maxDownloadBytes": 100000000, - "downloadTimeout": 120, + "maxDownloadBytes": 104857600, + "downloadTimeout": { + "connectionAndInactivity": "2m", + "wholeCycle": "2m" + }, "downloadAttemptMaxCount": 3, "downloadAttemptDelay": 1000, "maxprocesscount": 1, "fontDir": "null", "presentationThemesDir": "null", - "filePath": "null", + "x2tPath": "null", + "docbuilderPath": "null", "args": "", - "errorfiles": "" + "spawnOptions": {}, + "errorfiles": "", + "streamWriterBufferSize": 8388608, + "maxRedeliveredCount": 2, + "inputLimits": [ + { + "type": "docx;dotx;docm;dotm", + "zip": { + "uncompressed": "50MB", + "template": "*.xml" + } + }, + { + "type": "xlsx;xltx;xlsm;xltm", + "zip": { + "uncompressed": "300MB", + "template": "*.xml" + } + }, + { + "type": "pptx;ppsx;potx;pptm;ppsm;potm", + "zip": { + "uncompressed": "50MB", + "template": "*.xml" + } + } + ] } - }, - "FileStorage": { - "host": "", - "port": 4567, - "directory": "", - "silent": true - }, - "SpellChecker": { - "server": { - "port": 8080, - "mode": "development" - }, - "dictionaries": [ - {"name": "bg_BG", "id": 1026}, - {"name": "ca_ES", "id": 1027}, - {"name": "cs_CZ", "id": 1029}, - {"name": "da_DK", "id": 1030}, - {"name": "de_DE", "id": 1031}, - {"name": "el_GR", "id": 1032}, - {"name": "en_US", "id": 1033}, - {"name": "fr_FR", "id": 1036}, - {"name": "hu_HU", "id": 1038}, - {"name": "it_IT", "id": 1040}, - {"name": "ko_KR", "id": 1042}, - {"name": "nl_NL", "id": 1043}, - {"name": "nb_NO", "id": 1044}, - {"name": "pl_PL", "id": 1045}, - {"name": "pt_BR", "id": 1046}, - {"name": "ro_RO", "id": 1048}, - {"name": "ru_RU", "id": 1049}, - {"name": "hr_HR", "id": 1050}, - {"name": "sk_SK", "id": 1051}, - {"name": "sv_SE", "id": 1053}, - {"name": "tr_TR", "id": 1055}, - {"name": "id_ID", "id": 1057}, - {"name": "uk_UA", "id": 1058}, - {"name": "sl_SI", "id": 1060}, - {"name": "lv_LV", "id": 1062}, - {"name": "lt_LT", "id": 1063}, - {"name": "vi_VN", "id": 1066}, - {"name": "az_Latn_AZ", "id": 1068}, - {"name": "eu_ES", "id": 1069}, - {"name": "kk_KZ", "id": 1087}, - {"name": "mn_MN", "id": 1104}, - {"name": "gl_ES", "id": 1110}, - {"name": "lb_LU", "id": 1134}, - {"name": "ca_ES_valencia", "id": 2051}, - {"name": "de_CH", "id": 2055}, - {"name": "en_GB", "id": 2057}, - {"name": "nn_NO", "id": 2068}, - {"name": "pt_PT", "id": 2070}, - {"name": "de_AT", "id": 3079}, - {"name": "en_AU", "id": 3081}, - {"name": "es_ES", "id": 3082}, - {"name": "en_CA", "id": 4105}, - {"name": "en_ZA", "id": 7177}, - {"name": "sr_Latn_RS", "id": 9242}, - {"name": "sr_Cyrl_RS", "id": 10266} - ] } -} \ No newline at end of file +} diff --git a/Common/config/development-linux.json b/Common/config/development-linux.json new file mode 100644 index 0000000000..e004cd6b1c --- /dev/null +++ b/Common/config/development-linux.json @@ -0,0 +1,77 @@ +{ + "log": { + "filePath": "../Common/config/log4js/development.json" + }, + "storage": { + "fs": { + "folderPath": "../App_Data" + } + }, + "wopi": { + "enable": true + }, + "services": { + "CoAuthoring": { + "server": { + "port": 8000, + "static_content": { + "/fonts": { + "path": "../../fonts" + }, + "/sdkjs": { + "path": "../../sdkjs" + }, + "/web-apps": { + "path": "../../web-apps" + }, + "/sdkjs-plugins": { + "path": "../../sdkjs-plugins" + }, + "/dictionaries": { + "path": "../../dictionaries" + }, + "/welcome": { + "path": "../branding/welcome" + }, + "/info": { + "path": "../branding/info" + } + } + }, + "utils": { + "utils_common_fontdir": "/usr/share/fonts" + }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, + "sockjs": { + "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" + }, + "socketio": { + "connection": { + "pingTimeout": 86400000, + "pingInterval": 86400000 + } + } + } + }, + "license": { + "license_file": "./../license.lic", + "warning_limit_percents": 70, + "packageType": 0 + }, + "FileConverter": { + "converter": { + "fontDir": "/usr/share/fonts", + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/bin/x2t", + "docbuilderPath": "../FileConverter/bin/docbuilder" + } + }, + "SpellChecker": { + "server": { + "dictDir": "../../dictionaries" + } + } +} diff --git a/Common/config/development-mac.json b/Common/config/development-mac.json index 2507f320ec..b300483763 100644 --- a/Common/config/development-mac.json +++ b/Common/config/development-mac.json @@ -1,30 +1,40 @@ { "log": { - "filePath": "../../Common/config/log4js/development.json" + "filePath": "../Common/config/log4js/development.json" }, "storage": { - "name": "storage-fs", "fs": { - "folderPath": "../../App_Data", - "urlExpires": 2592000, - "secretString": "onlyoffice" + "folderPath": "../App_Data" } }, + "wopi": { + "enable": true + }, "services": { "CoAuthoring": { "server": { - "port": 8080, - "sockjsUrl": "/office/vendor/sockjs/sockjs.min.js", - "editor_settings_spellchecker_url": "", + "port": 8000, "static_content": { + "/fonts": { + "path": "../../fonts" + }, "/sdkjs": { - "path": "../../../sdkjs" + "path": "../../sdkjs" }, "/web-apps": { - "path": "../../../web-apps" + "path": "../../web-apps" }, "/sdkjs-plugins": { - "path": "../../../sdkjs-plugins" + "path": "../../sdkjs-plugins" + }, + "/dictionaries": { + "path": "../../dictionaries" + }, + "/welcome": { + "path": "../branding/welcome" + }, + "/info": { + "path": "../branding/info" } } }, @@ -36,18 +46,39 @@ "dbPort": 3306, "dbUser": "root", "dbPass": "onlyoffice" + }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, + "sockjs": { + "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" + }, + "socketio": { + "connection": { + "pingTimeout": 86400000, + "pingInterval": 86400000 + } } } }, "license": { - "license_file": "./../../license.lic" + "license_file": "./../license.lic", + "warning_limit_percents": 70, + "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "", - "presentationThemesDir": "../../../OfficeWeb/PowerPoint/themes", - "filePath": "../../FileConverter/Bin/x2t", + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/bin/x2t", + "docbuilderPath": "../FileConverter/Bin/docbuilder", "errorfiles": "error" } + }, + "SpellChecker": { + "server": { + "dictDir": "../../dictionaries" + } } } \ No newline at end of file diff --git a/Common/config/development-windows.json b/Common/config/development-windows.json index 0f261a8c42..66de30c115 100644 --- a/Common/config/development-windows.json +++ b/Common/config/development-windows.json @@ -1,30 +1,40 @@ { "log": { - "filePath": "../../Common/config/log4js/development.json" + "filePath": "../Common/config/log4js/development.json" }, "storage": { - "name": "storage-fs", "fs": { - "folderPath": "../../App_Data", - "urlExpires": 2592000, - "secretString": "onlyoffice" + "folderPath": "../App_Data" } }, + "wopi": { + "enable": true + }, "services": { "CoAuthoring": { "server": { - "port": 8001, - "sockjsUrl": "/web-apps/vendor/sockjs/sockjs.min.js", - "editor_settings_spellchecker_url": "http://127.0.0.1:8080", + "port": 8000, "static_content": { + "/fonts": { + "path": "../../fonts" + }, "/sdkjs": { - "path": "../../../sdkjs" + "path": "../../sdkjs" }, "/web-apps": { - "path": "../../../web-apps" + "path": "../../web-apps" }, "/sdkjs-plugins": { - "path": "../../../sdkjs-plugins" + "path": "../../sdkjs-plugins" + }, + "/dictionaries": { + "path": "../../dictionaries" + }, + "/welcome": { + "path": "../branding/welcome" + }, + "/info": { + "path": "../branding/info" } } }, @@ -36,18 +46,39 @@ "dbPort": 3306, "dbUser": "root", "dbPass": "onlyoffice" + }, + "request-filtering-agent" : { + "allowPrivateIPAddress": true, + "allowMetaIPAddress": true + }, + "sockjs": { + "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" + }, + "socketio": { + "connection": { + "pingTimeout": 86400000, + "pingInterval": 86400000 + } } } }, "license": { - "license_file": "./../../license.lic" + "license_file": "./../license.lic", + "warning_limit_percents": 70, + "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "", - "presentationThemesDir": "../../../sdkjs/slide/themes", - "filePath": "../../FileConverter/Bin/x2t.exe", + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/Bin/x2t.exe", + "docbuilderPath": "../FileConverter/Bin/docbuilder.exe", "errorfiles": "error" } + }, + "SpellChecker": { + "server": { + "dictDir": "../../dictionaries" + } } } \ No newline at end of file diff --git a/Common/config/log4js/development.json b/Common/config/log4js/development.json index 12e09a3930..67134f6a4b 100644 --- a/Common/config/log4js/development.json +++ b/Common/config/log4js/development.json @@ -1,13 +1,14 @@ { - "appenders": [{ - "type": "console", - "layout": { - "type": "pattern", - "pattern": "%[[%d] [%p] %c -%] %.10000m" + "appenders": { + "default": { + "type": "console", + "layout": { + "type": "pattern", + "pattern": "%[[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}] %c -%] %.10000m" + } } - }], - "replaceConsole": "true", - "levels": { - "nodeJS": "ALL" + }, + "categories": { + "default": { "appenders": [ "default" ], "level": "ALL" } } -} \ No newline at end of file +} diff --git a/Common/config/log4js/production.json b/Common/config/log4js/production.json index 7804695a6c..849430391b 100644 --- a/Common/config/log4js/production.json +++ b/Common/config/log4js/production.json @@ -1,13 +1,14 @@ { - "appenders": [{ - "type": "console", - "layout": { - "type": "pattern", - "pattern": "[%d] [%p] %c - %.10000m" + "appenders": { + "default": { + "type": "console", + "layout": { + "type": "pattern", + "pattern": "[%d] [%p] [%X{TENANT}] [%X{DOCID}] [%X{USERID}] %c - %.10000m" + } } - }], - "replaceConsole": "true", - "levels": { - "nodeJS": "WARN" + }, + "categories": { + "default": { "appenders": [ "default" ], "level": "WARN" } } } diff --git a/Common/config/production-linux.json b/Common/config/production-linux.json index 6990f33bad..0cb4dc6522 100644 --- a/Common/config/production-linux.json +++ b/Common/config/production-linux.json @@ -3,18 +3,22 @@ "filePath": "/etc/onlyoffice/documentserver/log4js/production.json" }, "storage": { - "name": "storage-fs", "fs": { - "folderPath": "/var/lib/onlyoffice/documentserver/App_Data/cache/files", - "urlExpires": 2592000, - "secretString": "onlyoffice" + "folderPath": "/var/lib/onlyoffice/documentserver/App_Data/cache/files" } }, + "wopi": { + "htmlTemplate" : "/var/www/onlyoffice/documentserver/web-apps/apps/api/wopi" + }, "services": { "CoAuthoring": { "server": { - "sockjsUrl": "/web-apps/vendor/sockjs/sockjs.min.js", + "newFileTemplate" : "/var/www/onlyoffice/documentserver/document-templates/new", "static_content": { + "/fonts": { + "path": "/var/www/onlyoffice/documentserver/fonts", + "options": {"maxAge": "7d"} + }, "/sdkjs": { "path": "/var/www/onlyoffice/documentserver/sdkjs", "options": {"maxAge": "7d"} @@ -27,29 +31,44 @@ "path": "/var/www/onlyoffice/documentserver/server/welcome", "options": {"maxAge": "7d"} }, + "/info": { + "path": "/var/www/onlyoffice/documentserver/server/info", + "options": {"maxAge": "7d"} + }, "/sdkjs-plugins": { "path": "/var/www/onlyoffice/documentserver/sdkjs-plugins", "options": {"maxAge": "7d"} + }, + "/dictionaries": { + "path": "/var/www/onlyoffice/documentserver/dictionaries", + "options": {"maxAge": "7d"} } } }, "utils": { "utils_common_fontdir": "/usr/share/fonts" + }, + "sockjs": { + "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" } } }, "license": { - "license_file": "/var/www/onlyoffice/Data/license.lic" + "license_file": "/var/www/onlyoffice/documentserver/../Data/license.lic", + "warning_limit_percents": 70, + "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "/usr/share/fonts", "presentationThemesDir": "/var/www/onlyoffice/documentserver/sdkjs/slide/themes", - "filePath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t", - "errorfiles": "error" + "x2tPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/x2t", + "docbuilderPath": "/var/www/onlyoffice/documentserver/server/FileConverter/bin/docbuilder" } }, - "FileStorage": { - "directory": "/var/lib/onlyoffice/documentserver/App_Data" + "SpellChecker": { + "server": { + "dictDir": "/var/www/onlyoffice/documentserver/dictionaries" + } } } diff --git a/Common/config/production-windows.json b/Common/config/production-windows.json index 2469f3e6d4..648038ea61 100644 --- a/Common/config/production-windows.json +++ b/Common/config/production-windows.json @@ -1,52 +1,70 @@ { "log": { - "filePath": "../../../config/log4js/production.json" + "filePath": "../../config/log4js/production.json" }, "storage": { - "name": "storage-fs", "fs": { - "folderPath": "../../App_Data/cache/files", - "urlExpires": 2592000, - "secretString": "onlyoffice" + "folderPath": "../App_Data/cache/files" } }, "services": { "CoAuthoring": { "server": { - "sockjsUrl": "/web-apps/vendor/sockjs/sockjs.min.js", "static_content": { + "/fonts": { + "path": "../../fonts", + "options": {"maxAge": "7d"} + }, "/sdkjs": { - "path": "../../../sdkjs", + "path": "../../sdkjs", "options": {"maxAge": "7d"} }, "/web-apps": { - "path": "../../../web-apps", + "path": "../../web-apps", "options": {"maxAge": "7d"} }, "/sdkjs-plugins": { - "path": "../../../sdkjs-plugins", + "path": "../../sdkjs-plugins", + "options": {"maxAge": "7d"} + }, + "/dictionaries": { + "path": "../../dictionaries", "options": {"maxAge": "7d"} }, "/welcome": { - "path": "../../welcome", + "path": "../welcome", + "options": {"maxAge": "7d"} + }, + "/info": { + "path": "../info", "options": {"maxAge": "7d"} } } }, "utils": { "utils_common_fontdir": "C:\\Windows\\Fonts" + }, + "sockjs": { + "sockjs_url": "/web-apps/vendor/sockjs/sockjs.min.js" } } }, "license": { - "license_file": "./../../license.lic" + "license_file": "./../license.lic", + "warning_limit_percents": 70, + "packageType": 0 }, "FileConverter": { "converter": { "fontDir": "", - "presentationThemesDir": "../../../sdkjs/slide/themes", - "filePath": "../../FileConverter/bin/x2t.exe", - "errorfiles": "error" + "presentationThemesDir": "../../sdkjs/slide/themes", + "x2tPath": "../FileConverter/bin/x2t.exe", + "docbuilderPath": "../FileConverter/bin/docbuilder.exe" + } + }, + "SpellChecker": { + "server": { + "dictDir": "../../dictionaries" } } } diff --git a/Common/install/InstallAndRunService.bat b/Common/install/InstallAndRunService.bat deleted file mode 100644 index de511a173b..0000000000 --- a/Common/install/InstallAndRunService.bat +++ /dev/null @@ -1,53 +0,0 @@ -ECHO OFF -SET RUN_FOLDER=%CD% -SET SERVICE_NAME=%1 -SET SERVICE_NAME=%SERVICE_NAME:"=% -SET EXE_PATH=%2 -SET EXE_PATH=%EXE_PATH:"=% - -rem ECHO. -rem ECHO ---------------------------------------- -rem ECHO Check is the script run under administrator %RUN_FOLDER% -rem ECHO ---------------------------------------- - -rem if not "%RUN_FOLDER%"=="%WinDir%\system32" GOTO ERROR - -SET INSTSRV=instsrv.exe -SET WINDOWS_RESOURCE_KIT_SUBPATH=Windows Resource Kits\Tools -SET WINDOWS_RESOURCE_KIT_PATH=%ProgramFiles(x86)%\%WINDOWS_RESOURCE_KIT_SUBPATH% - -SET TMP_REG_FILE=tmp.reg - -if not exist "%WINDOWS_RESOURCE_KIT_PATH%\%INSTSRV%" SET WINDOWS_RESOURCE_KIT_PATH=%ProgramFiles%\%WINDOWS_RESOURCE_KIT_SUBPATH% - -if not exist "%WINDOWS_RESOURCE_KIT_PATH%\%INSTSRV%" goto ERROR - -CD /D "%WINDOWS_RESOURCE_KIT_PATH%" || goto ERROR - -ECHO. -ECHO ---------------------------------------- -ECHO Install %EXE_PATH% as "%SERVICE_NAME%" service -ECHO ---------------------------------------- - -"%WINDOWS_RESOURCE_KIT_PATH%\%INSTSRV%" "%SERVICE_NAME%" "%WINDOWS_RESOURCE_KIT_PATH%\srvany.exe" || goto ERROR - -ECHO Windows Registry Editor Version 5.00 > %TMP_REG_FILE% -ECHO [HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\%SERVICE_NAME%\Parameters] >> %TMP_REG_FILE% -ECHO "Application"="%EXE_PATH:\=\\%" >> %TMP_REG_FILE% - -regedit.exe /s %TMP_REG_FILE% || goto ERROR - -ECHO. -ECHO ---------------------------------------- -ECHO Start "%SERVICE_NAME%" service -ECHO ---------------------------------------- -call sc start "%SERVICE_NAME%" || goto ERROR - -DEL %TMP_REG_FILE% -CD /D %RUN_FOLDER% - -:SUCCESS -exit /b 0 - -:ERROR -exit /b 1 diff --git a/Common/install/InstallNodeJSDebugModule.bat b/Common/install/InstallNodeJSDebugModule.bat deleted file mode 100644 index 56d570b34f..0000000000 --- a/Common/install/InstallNodeJSDebugModule.bat +++ /dev/null @@ -1,16 +0,0 @@ -ECHO OFF - -SET RUN_FOLDER=%CD% - -CD /D %~dp0..\ || exit /b 1 - -ECHO. -ECHO ---------------------------------------- -ECHO Install node.js module debug -ECHO ---------------------------------------- - -call npm install node-inspector || exit /b 1 - -CD /D %RUN_FOLDER% || exit /b 1 - -exit /b 0 diff --git a/Common/install/StartNodeInspector.bat b/Common/install/StartNodeInspector.bat deleted file mode 100644 index 8a6df49d9d..0000000000 --- a/Common/install/StartNodeInspector.bat +++ /dev/null @@ -1,2 +0,0 @@ -call node-inspector --web-port=8080 & -pause \ No newline at end of file diff --git a/Common/npm-shrinkwrap.json b/Common/npm-shrinkwrap.json new file mode 100644 index 0000000000..018e87c8f4 --- /dev/null +++ b/Common/npm-shrinkwrap.json @@ -0,0 +1,5788 @@ +{ + "name": "common", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "requires": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "requires": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "requires": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + } + } + }, + "@aws-sdk/client-s3": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.637.0.tgz", + "integrity": "sha512-y6UC94fsMvhKbf0dzfnjVP1HePeGjplfcYfilZU1COIJLyTkMcUv4XcT4I407CGIrvgEafONHkiC09ygqUauNA==", + "requires": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/client-sts": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-bucket-endpoint": "3.620.0", + "@aws-sdk/middleware-expect-continue": "3.620.0", + "@aws-sdk/middleware-flexible-checksums": "3.620.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-location-constraint": "3.609.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/middleware-ssec": "3.609.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/signature-v4-multi-region": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@aws-sdk/xml-builder": "3.609.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/eventstream-serde-browser": "^3.0.6", + "@smithy/eventstream-serde-config-resolver": "^3.0.3", + "@smithy/eventstream-serde-node": "^3.0.5", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-blob-browser": "^3.1.2", + "@smithy/hash-node": "^3.0.3", + "@smithy/hash-stream-node": "^3.1.2", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/md5-js": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "@smithy/util-waiter": "^3.1.2", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/signature-v4-multi-region": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.635.0.tgz", + "integrity": "sha512-J6QY4/invOkpogCHjSaDON1hF03viPpOnsrzVuCvJMmclS/iG62R4EY0wq1alYll0YmSdmKlpJwHMWwGtqK63Q==", + "requires": { + "@aws-sdk/middleware-sdk-s3": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", + "integrity": "sha512-+KjLvgX5yJYROWo3TQuwBJlHCY0zz9PsLuEolmXQn0BVK1L/m9GteZHtd+rEdAoDGBpE0Xqjy1oz5+SmtsaRUw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-sso-oidc": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.637.0.tgz", + "integrity": "sha512-27bHALN6Qb6m6KZmPvRieJ/QRlj1lyac/GT2Rn5kJpre8Mpp+yxrtvp3h9PjNBty4lCeFEENfY4dGNSozBuBcw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/client-sts": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", + "integrity": "sha512-xUi7x4qDubtA8QREtlblPuAcn91GS/09YVEY/RwU7xCY0aqGuFwgszAANlha4OUIqva8oVj2WO4gJuG+iaSnhw==", + "requires": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/client-sso-oidc": "3.637.0", + "@aws-sdk/core": "3.635.0", + "@aws-sdk/credential-provider-node": "3.637.0", + "@aws-sdk/middleware-host-header": "3.620.0", + "@aws-sdk/middleware-logger": "3.609.0", + "@aws-sdk/middleware-recursion-detection": "3.620.0", + "@aws-sdk/middleware-user-agent": "3.637.0", + "@aws-sdk/region-config-resolver": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@aws-sdk/util-user-agent-browser": "3.609.0", + "@aws-sdk/util-user-agent-node": "3.614.0", + "@smithy/config-resolver": "^3.0.5", + "@smithy/core": "^2.4.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/hash-node": "^3.0.3", + "@smithy/invalid-dependency": "^3.0.3", + "@smithy/middleware-content-length": "^3.0.5", + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-body-length-node": "^3.0.0", + "@smithy/util-defaults-mode-browser": "^3.0.15", + "@smithy/util-defaults-mode-node": "^3.0.15", + "@smithy/util-endpoints": "^2.0.5", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/core": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", + "integrity": "sha512-i1x/E/sgA+liUE1XJ7rj1dhyXpAKO1UKFUcTTHXok2ARjWTvszHnSXMOsB77aPbmn0fUp1JTx2kHUAZ1LVt5Bg==", + "requires": { + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", + "integrity": "sha512-ExuILJ2qLW5ZO+rgkNRj0xiAipKT16Rk77buvPP8csR7kkCflT/gXTyzRe/uzIiETTxM7tr8xuO9MP/DQXqkfg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-http": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.635.0.tgz", + "integrity": "sha512-iJyRgEjOCQlBMXqtwPLIKYc7Bsc6nqjrZybdMDenPDa+kmLg7xh8LxHsu9088e+2/wtLicE34FsJJIfzu3L82g==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", + "integrity": "sha512-h+PFCWfZ0Q3Dx84SppET/TFpcQHmxFW8/oV9ArEvMilw4EBN+IlxgbL0CnHwjHW64szcmrM0mbebjEfHf4FXmw==", + "requires": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.637.0.tgz", + "integrity": "sha512-yoEhoxJJfs7sPVQ6Is939BDQJZpZCoUgKr/ySse4YKOZ24t4VqgHA6+wV7rYh+7IW24Rd91UTvEzSuHYTlxlNA==", + "requires": { + "@aws-sdk/credential-provider-env": "3.620.1", + "@aws-sdk/credential-provider-http": "3.635.0", + "@aws-sdk/credential-provider-ini": "3.637.0", + "@aws-sdk/credential-provider-process": "3.620.1", + "@aws-sdk/credential-provider-sso": "3.637.0", + "@aws-sdk/credential-provider-web-identity": "3.621.0", + "@aws-sdk/types": "3.609.0", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.620.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.620.1.tgz", + "integrity": "sha512-hWqFMidqLAkaV9G460+1at6qa9vySbjQKKc04p59OT7lZ5cO5VH5S4aI05e+m4j364MBROjjk2ugNvfNf/8ILg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.637.0.tgz", + "integrity": "sha512-Mvz+h+e62/tl+dVikLafhv+qkZJ9RUb8l2YN/LeKMWkxQylPT83CPk9aimVhCV89zth1zpREArl97+3xsfgQvA==", + "requires": { + "@aws-sdk/client-sso": "3.637.0", + "@aws-sdk/token-providers": "3.614.0", + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.621.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.621.0.tgz", + "integrity": "sha512-w7ASSyfNvcx7+bYGep3VBgC3K6vEdLmlpjT7nSIHxxQf+WSdvy+HynwJosrpZax0sK5q0D1Jpn/5q+r5lwwW6w==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-bucket-endpoint": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.620.0.tgz", + "integrity": "sha512-eGLL0W6L3HDb3OACyetZYOWpHJ+gLo0TehQKeQyy2G8vTYXqNTeqYhuI6up9HVjBzU9eQiULVQETmgQs7TFaRg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-expect-continue": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.620.0.tgz", + "integrity": "sha512-QXeRFMLfyQ31nAHLbiTLtk0oHzG9QLMaof5jIfqcUwnOkO8YnQdeqzakrg1Alpy/VQ7aqzIi8qypkBe2KXZz0A==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-flexible-checksums": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.620.0.tgz", + "integrity": "sha512-ftz+NW7qka2sVuwnnO1IzBku5ccP+s5qZGeRTPgrKB7OzRW85gthvIo1vQR2w+OwHFk7WJbbhhWwbCbktnP4UA==", + "requires": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-sdk/types": "3.609.0", + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + } + } + }, + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + } + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + } + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.620.0.tgz", + "integrity": "sha512-VMtPEZwqYrII/oUkffYsNWY9PZ9xpNJpMgmyU0rlDQ25O1c0Hk3fJmZRe6pEkAJ0omD7kLrqGl1DUjQVxpd/Rg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-location-constraint": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.609.0.tgz", + "integrity": "sha512-xzsdoTkszGVqGVPjUmgoP7TORiByLueMHieI1fhQL888WPdqctwAx3ES6d/bA9Q/i8jnc6hs+Fjhy8UvBTkE9A==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", + "integrity": "sha512-S62U2dy4jMDhDFDK5gZ4VxFdWzCtLzwbYyFZx2uvPYTECkepLUfzLic2BHg2Qvtu4QjX+oGE3P/7fwaGIsGNuQ==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.620.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.620.0.tgz", + "integrity": "sha512-nh91S7aGK3e/o1ck64sA/CyoFw+gAYj2BDOnoNa6ouyCrVJED96ZXWbhye/fz9SgmNUZR2g7GdVpiLpMKZoI5w==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-sdk-s3": { + "version": "3.635.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.635.0.tgz", + "integrity": "sha512-RLdYJPEV4JL/7NBoFUs7VlP90X++5FlJdxHz0DzCjmiD3qCviKy+Cym3qg1gBgHwucs5XisuClxDrGokhAdTQw==", + "requires": { + "@aws-sdk/core": "3.635.0", + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-arn-parser": "3.568.0", + "@smithy/core": "^2.4.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/signature-v4": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-stream": "^3.1.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/signature-v4": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", + "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-ssec": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.609.0.tgz", + "integrity": "sha512-GZSD1s7+JswWOTamVap79QiDaIV7byJFssBW68GYjyRS5EBjNfwA/8s+6uE6g39R3ojyTbYOmvcANoZEhSULXg==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", + "integrity": "sha512-EYo0NE9/da/OY8STDsK2LvM4kNa79DBsf4YVtaG4P5pZ615IeFsD8xOHZeuJmUrSMlVQ8ywPRX7WMucUybsKug==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@aws-sdk/util-endpoints": "3.637.0", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/node-http-handler": { + "version": "3.374.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/node-http-handler/-/node-http-handler-3.374.0.tgz", + "integrity": "sha512-v1Z6m0wwkf65/tKuhwrtPRqVoOtNkDTRn2MBMtxCwEw+8V8Q+YRFqVgGN+J1n53ktE0G5OYVBux/NHiAjJHReQ==", + "requires": { + "@smithy/node-http-handler": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/region-config-resolver": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", + "integrity": "sha512-vDCeMXvic/LU0KFIUjpC3RiSTIkkvESsEfbVHiHH0YINfl8HnEqR5rj+L8+phsCeVg2+LmYwYxd5NRz4PHxt5g==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/s3-request-presigner": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.370.0.tgz", + "integrity": "sha512-ilTf+Q3nEKBd6soJo8cAGDCoRV6ggLYxUHqLyVXreMsio9rog7H2ePbFtO9gtbwUGR46gqgDQyIf+7i7yAuP6A==", + "requires": { + "@aws-sdk/signature-v4-multi-region": "3.370.0", + "@aws-sdk/types": "3.370.0", + "@aws-sdk/util-format-url": "3.370.0", + "@smithy/middleware-endpoint": "^1.0.2", + "@smithy/protocol-http": "^1.1.0", + "@smithy/smithy-client": "^1.0.3", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/signature-v4-multi-region": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.370.0.tgz", + "integrity": "sha512-Q3NQopPDnHbJXMhtYl0Mfy5U2o76K6tzhdnYRcrYImY0ze/zOkCQI7KPC4588PuyvAXCdQ02cmCPPjYD55UeNg==", + "requires": { + "@aws-sdk/types": "3.370.0", + "@smithy/protocol-http": "^1.1.0", + "@smithy/signature-v4": "^1.0.1", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/token-providers": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.614.0.tgz", + "integrity": "sha512-okItqyY6L9IHdxqs+Z116y5/nda7rHxLvROxtAJdLavWTYDydxrZstImNgGWTeVdmc0xX2gJCI77UYUTQWnhRw==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "requires": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/util-arn-parser": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.568.0.tgz", + "integrity": "sha512-XUKJWWo+KOB7fbnPP0+g/o5Ulku/X53t7i/h+sPHr5xxYTJJ9CYnbToo95mzxe7xWvkLrsNtJ8L+MnNn9INs2w==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.637.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.637.0.tgz", + "integrity": "sha512-pAqOKUHeVWHEXXDIp/qoMk/6jyxIb6GGjnK1/f8dKHtKIEs4tKsnnL563gceEvdad53OPXIt86uoevCcCzmBnw==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "@smithy/util-endpoints": "^2.0.5", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-format-url": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.370.0.tgz", + "integrity": "sha512-6ik8UTT5hNlMnATrqWiQWnIZ0EFW8wVsRGyYUYw/geB3lQ+WAWflpJg+gZiJnc5EY4R0aOzRVm02W8EUeH8f5g==", + "requires": { + "@aws-sdk/types": "3.370.0", + "@smithy/querystring-builder": "^1.0.1", + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/util-locate-window": { + "version": "3.568.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.568.0.tgz", + "integrity": "sha512-3nh4TINkXYr+H41QaPelCceEB2FXP3fxp93YZXB/kqJvX0U9j0N0Uk45gvsjmEPzG8XxkPEeLIfT2I1M7A6Lig==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.609.0.tgz", + "integrity": "sha512-fojPU+mNahzQ0YHYBsx0ZIhmMA96H+ZIZ665ObU9tl+SGdbLneVZVikGve+NmHTQwHzwkFsZYYnVKAkreJLAtA==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.614.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.614.0.tgz", + "integrity": "sha512-15ElZT88peoHnq5TEoEtZwoXTXRxNrk60TZNdpl/TUBJ5oNJ9Dqb5Z4ryb8ofN6nm9aFf59GVAerFDz8iUoHBA==", + "requires": { + "@aws-sdk/types": "3.609.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-sdk/types": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.609.0.tgz", + "integrity": "sha512-+Tqnh9w0h2LcrUsdXyT1F8mNhXz+tVYBtP19LpeEGntmvHwa2XzvLUCWpoIAIVsHp5+HdB2X9Sn0KAtmbFXc2Q==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "requires": { + "tslib": "^2.3.1" + } + }, + "@aws-sdk/xml-builder": { + "version": "3.609.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.609.0.tgz", + "integrity": "sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/abort-controller": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-1.0.2.tgz", + "integrity": "sha512-tb2h0b+JvMee+eAxTmhnyqyNk51UXIK949HnE14lFeezKsVJTB30maan+CO2IMwnig2wVYQH84B5qk6ylmKCuA==", + "requires": { + "@smithy/types": "^1.1.1", + "tslib": "^2.5.0" + } + }, + "@smithy/chunked-blob-reader": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-3.0.0.tgz", + "integrity": "sha512-sbnURCwjF0gSToGlsBiAmd1lRCmSn72nu9axfJu5lIx6RUEgHu6GwTMbqCdhQSi0Pumcm5vFxsi9XWXb2mTaoA==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/chunked-blob-reader-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-3.0.0.tgz", + "integrity": "sha512-VDkpCYW+peSuM4zJip5WDfqvg2Mo/e8yxOv3VF1m11y7B8KKMKVFtmZWDe36Fvk8rGuWrPZHHXZ7rR7uM5yWyg==", + "requires": { + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/config-resolver": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-3.0.5.tgz", + "integrity": "sha512-SkW5LxfkSI1bUC74OtfBbdz+grQXYiPYolyu8VfpLIjEoN/sHVBlLeGXMQ1vX4ejkgfv6sxVbQJ32yF2cl1veA==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-config-provider": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/core": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-2.4.0.tgz", + "integrity": "sha512-cHXq+FneIF/KJbt4q4pjN186+Jf4ZB0ZOqEaZMBhT79srEyGDDBV31NqBRBjazz8ppQ1bJbDJMY9ba5wKFV36w==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-retry": "^3.0.15", + "@smithy/middleware-serde": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-body-length-browser": "^3.0.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/credential-provider-imds": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", + "integrity": "sha512-0SCIzgd8LYZ9EJxUjLXBmEKSZR/P/w6l7Rz/pab9culE/RWuqelAKGJvn5qUOl8BgX8Yj5HWM50A5hiB/RzsgA==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-codec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-1.0.2.tgz", + "integrity": "sha512-eW/XPiLauR1VAgHKxhVvgvHzLROUgTtqat2lgljztbH8uIYWugv7Nz+SgCavB+hWRazv2iYgqrSy74GvxXq/rg==", + "requires": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^1.1.1", + "@smithy/util-hex-encoding": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/eventstream-serde-browser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-3.0.6.tgz", + "integrity": "sha512-2hM54UWQUOrki4BtsUI1WzmD13/SeaqT/AB3EUJKbcver/WgKNaiJ5y5F5XXuVe6UekffVzuUDrBZVAA3AWRpQ==", + "requires": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-config-resolver": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-3.0.3.tgz", + "integrity": "sha512-NVTYjOuYpGfrN/VbRQgn31x73KDLfCXCsFdad8DiIc3IcdxL+dYA9zEQPyOP7Fy2QL8CPy2WE4WCUD+ZsLNfaQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-node": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-3.0.5.tgz", + "integrity": "sha512-+upXvnHNyZP095s11jF5dhGw/Ihzqwl5G+/KtMnoQOpdfC3B5HYCcDVG9EmgkhJMXJlM64PyN5gjJl0uXFQehQ==", + "requires": { + "@smithy/eventstream-serde-universal": "^3.0.5", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/eventstream-serde-universal": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-3.0.5.tgz", + "integrity": "sha512-5u/nXbyoh1s4QxrvNre9V6vfyoLWuiVvvd5TlZjGThIikc3G+uNiG9uOTCWweSRjv1asdDIWK7nOmN7le4RYHQ==", + "requires": { + "@smithy/eventstream-codec": "^3.1.2", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "requires": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "requires": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/eventstream-codec": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-3.1.2.tgz", + "integrity": "sha512-0mBcu49JWt4MXhrhRAlxASNy0IjDRFU+aWNDRal9OtUJvJNiwDuyKMUONSOjLjSCeGwZaE0wOErdqULer8r7yw==", + "requires": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-hex-encoding": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "requires": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "requires": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/fetch-http-handler": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-1.0.2.tgz", + "integrity": "sha512-kynyofLf62LvR8yYphPPdyHb8fWG3LepFinM/vWUTG2Q1pVpmPCM530ppagp3+q2p+7Ox0UvSqldbKqV/d1BpA==", + "requires": { + "@smithy/protocol-http": "^1.1.1", + "@smithy/querystring-builder": "^1.0.2", + "@smithy/types": "^1.1.1", + "@smithy/util-base64": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/hash-blob-browser": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-3.1.2.tgz", + "integrity": "sha512-hAbfqN2UbISltakCC2TP0kx4LqXBttEv2MqSPE98gVuDFMf05lU+TpC41QtqGP3Ff5A3GwZMPfKnEy0VmEUpmg==", + "requires": { + "@smithy/chunked-blob-reader": "^3.0.0", + "@smithy/chunked-blob-reader-native": "^3.0.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/hash-node": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", + "integrity": "sha512-2ctBXpPMG+B3BtWSGNnKELJ7SH9e4TNefJS0cd2eSkOOROeBnnVBnAy9LtJ8tY4vUEoe55N4CNPxzbWvR39iBw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/hash-stream-node": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-3.1.2.tgz", + "integrity": "sha512-PBgDMeEdDzi6JxKwbfBtwQG9eT9cVwsf0dZzLXoJF4sHKHs5HEo/3lJWpn6jibfJwT34I1EBXpBnZE8AxAft6g==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/invalid-dependency": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", + "integrity": "sha512-ID1eL/zpDULmHJbflb864k72/SNOZCADRc9i7Exq3RUNJw6raWUSlFEQ+3PX3EYs++bTxZB2dE9mEHTQLv61tw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/is-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-1.0.2.tgz", + "integrity": "sha512-pkyBnsBRpe+c/6ASavqIMRBdRtZNJEVJOEzhpxZ9JoAXiZYbkfaSMRA/O1dUxGdJ653GHONunnZ4xMo/LJ7utQ==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/md5-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-3.0.3.tgz", + "integrity": "sha512-O/SAkGVwpWmelpj/8yDtsaVe6sINHLB1q8YE/+ZQbDxIw3SRLbTZuRaI10K12sVoENdnHqzPp5i3/H+BcZ3m3Q==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-content-length": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-3.0.5.tgz", + "integrity": "sha512-ILEzC2eyxx6ncej3zZSwMpB5RJ0zuqH7eMptxC4KN3f+v9bqT8ohssKbhNR78k/2tWW+KS5Spw+tbPF4Ejyqvw==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-endpoint": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-1.0.3.tgz", + "integrity": "sha512-GsWvTXMFjSgl617PCE2km//kIjjtvMRrR2GAuRDIS9sHiLwmkS46VWaVYy+XE7ubEsEtzZ5yK2e8TKDR6Qr5Lw==", + "requires": { + "@smithy/middleware-serde": "^1.0.2", + "@smithy/types": "^1.1.1", + "@smithy/url-parser": "^1.0.2", + "@smithy/util-middleware": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-retry": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-3.0.15.tgz", + "integrity": "sha512-iTMedvNt1ApdvkaoE8aSDuwaoc+BhvHqttbA/FO4Ty+y/S5hW6Ci/CTScG7vam4RYJWZxdTElc3MEfHRVH6cgQ==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/protocol-http": "^4.1.0", + "@smithy/service-error-classification": "^3.0.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "@smithy/util-middleware": "^3.0.3", + "@smithy/util-retry": "^3.0.3", + "tslib": "^2.6.2", + "uuid": "^9.0.1" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/middleware-serde": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-1.0.2.tgz", + "integrity": "sha512-T4PcdMZF4xme6koUNfjmSZ1MLi7eoFeYCtodQNQpBNsS77TuJt1A6kt5kP/qxrTvfZHyFlj0AubACoaUqgzPeg==", + "requires": { + "@smithy/types": "^1.1.1", + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-stack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-1.0.2.tgz", + "integrity": "sha512-H7/uAQEcmO+eDqweEFMJ5YrIpsBwmrXSP6HIIbtxKJSQpAcMGY7KrR2FZgZBi1FMnSUOh+rQrbOyj5HQmSeUBA==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/node-config-provider": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz", + "integrity": "sha512-YvnElQy8HR4vDcAjoy7Xkx9YT8xZP4cBXcbJSgm/kxmiQu08DwUwj8rkGnyoJTpfl/3xYHH+d8zE+eHqoDCSdQ==", + "requires": { + "@smithy/property-provider": "^3.1.3", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/node-http-handler": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-1.0.3.tgz", + "integrity": "sha512-PcPUSzTbIb60VCJCiH0PU0E6bwIekttsIEf5Aoo/M0oTfiqsxHTn0Rcij6QoH6qJy6piGKXzLSegspXg5+Kq6g==", + "requires": { + "@smithy/abort-controller": "^1.0.2", + "@smithy/protocol-http": "^1.1.1", + "@smithy/querystring-builder": "^1.0.2", + "@smithy/types": "^1.1.1", + "tslib": "^2.5.0" + } + }, + "@smithy/property-provider": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/protocol-http": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-1.1.1.tgz", + "integrity": "sha512-mFLFa2sSvlUxm55U7B4YCIsJJIMkA6lHxwwqOaBkral1qxFz97rGffP/mmd4JDuin1EnygiO5eNJGgudiUgmDQ==", + "requires": { + "@smithy/types": "^1.1.1", + "tslib": "^2.5.0" + } + }, + "@smithy/querystring-builder": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-1.0.2.tgz", + "integrity": "sha512-6P/xANWrtJhMzTPUR87AbXwSBuz1SDHIfL44TFd/GT3hj6rA+IEv7rftEpPjayUiWRocaNnrCPLvmP31mobOyA==", + "requires": { + "@smithy/types": "^1.1.1", + "@smithy/util-uri-escape": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/querystring-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-1.0.2.tgz", + "integrity": "sha512-IWxwxjn+KHWRRRB+K2Ngl+plTwo2WSgc2w+DvLy0DQZJh9UGOpw40d6q97/63GBlXIt4TEt5NbcFrO30CKlrsA==", + "requires": { + "@smithy/types": "^1.1.1", + "tslib": "^2.5.0" + } + }, + "@smithy/service-error-classification": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-3.0.3.tgz", + "integrity": "sha512-Jn39sSl8cim/VlkLsUhRFq/dKDnRUFlfRkvhOJaUbLBXUsLRLNf9WaxDv/z9BjuQ3A6k/qE8af1lsqcwm7+DaQ==", + "requires": { + "@smithy/types": "^3.3.0" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/shared-ini-file-loader": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz", + "integrity": "sha512-qMxS4hBGB8FY2GQqshcRUy1K6k8aBWP5vwm8qKkCT3A9K2dawUwOIJfqh9Yste/Bl0J2lzosVyrXDj68kLcHXQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/signature-v4": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-1.0.2.tgz", + "integrity": "sha512-rpKUhmCuPmpV5dloUkOb9w1oBnJatvKQEjIHGmkjRGZnC3437MTdzWej9TxkagcZ8NRRJavYnEUixzxM1amFig==", + "requires": { + "@smithy/eventstream-codec": "^1.0.2", + "@smithy/is-array-buffer": "^1.0.2", + "@smithy/types": "^1.1.1", + "@smithy/util-hex-encoding": "^1.0.2", + "@smithy/util-middleware": "^1.0.2", + "@smithy/util-uri-escape": "^1.0.2", + "@smithy/util-utf8": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/smithy-client": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-1.0.4.tgz", + "integrity": "sha512-gpo0Xl5Nyp9sgymEfpt7oa9P2q/GlM3VmQIdm+FeH0QEdYOQx3OtvwVmBYAMv2FIPWxkMZlsPYRTnEiBTK5TYg==", + "requires": { + "@smithy/middleware-stack": "^1.0.2", + "@smithy/types": "^1.1.1", + "@smithy/util-stream": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/types": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.1.1.tgz", + "integrity": "sha512-tMpkreknl2gRrniHeBtdgQwaOlo39df8RxSrwsHVNIGXULy5XP6KqgScUw2m12D15wnJCKWxVhCX+wbrBW/y7g==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/url-parser": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-1.0.2.tgz", + "integrity": "sha512-0JRsDMQe53F6EHRWksdcavKDRjyqp8vrjakg8EcCUOa7PaFRRB1SO/xGZdzSlW1RSTWQDEksFMTCEcVEKmAoqA==", + "requires": { + "@smithy/querystring-parser": "^1.0.2", + "@smithy/types": "^1.1.1", + "tslib": "^2.5.0" + } + }, + "@smithy/util-base64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-1.0.2.tgz", + "integrity": "sha512-BCm15WILJ3SL93nusoxvJGMVfAMWHZhdeDZPtpAaskozuexd0eF6szdz4kbXaKp38bFCSenA6bkUHqaE3KK0dA==", + "requires": { + "@smithy/util-buffer-from": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/util-body-length-browser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", + "integrity": "sha512-cbjJs2A1mLYmqmyVl80uoLTJhAcfzMOyPgjwAYusWKMdLeNtzmMz9YxNl3/jRLoxSS3wkqkf0jwNdtXWtyEBaQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-body-length-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-3.0.0.tgz", + "integrity": "sha512-Tj7pZ4bUloNUP6PzwhN7K386tmSmEET9QtQg0TgdNOnxhZvCssHji+oZTUIuzxECRfG8rdm2PMw2WCFs6eIYkA==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-buffer-from": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-1.0.2.tgz", + "integrity": "sha512-lHAYIyrBO9RANrPvccnPjU03MJnWZ66wWuC5GjWWQVfsmPwU6m00aakZkzHdUT6tGCkGacXSgArP5wgTgA+oCw==", + "requires": { + "@smithy/is-array-buffer": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/util-config-provider": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-3.0.0.tgz", + "integrity": "sha512-pbjk4s0fwq3Di/ANL+rCvJMKM5bzAQdE5S/6RL5NXgMExFAi6UgQMPOm5yPaIWPpr+EOXKXRonJ3FoxKf4mCJQ==", + "requires": { + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-defaults-mode-browser": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-3.0.15.tgz", + "integrity": "sha512-FZ4Psa3vjp8kOXcd3HJOiDPBCWtiilLl57r0cnNtq/Ga9RSDrM5ERL6xt+tO43+2af6Pn5Yp92x2n5vPuduNfg==", + "requires": { + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-defaults-mode-node": { + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-3.0.15.tgz", + "integrity": "sha512-KSyAAx2q6d0t6f/S4XB2+3+6aQacm3aLMhs9aLMqn18uYGUepbdssfogW5JQZpc6lXNBnp0tEnR5e9CEKmEd7A==", + "requires": { + "@smithy/config-resolver": "^3.0.5", + "@smithy/credential-provider-imds": "^3.2.0", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/property-provider": "^3.1.3", + "@smithy/smithy-client": "^3.2.0", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/fetch-http-handler": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz", + "integrity": "sha512-kBprh5Gs5h7ug4nBWZi1FZthdqSM+T7zMmsZxx0IBvWUn7dK3diz2SHn7Bs4dQGFDk8plDv375gzenDoNwrXjg==", + "requires": { + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/is-array-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-3.0.0.tgz", + "integrity": "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-endpoint": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", + "integrity": "sha512-5y5aiKCEwg9TDPB4yFE7H6tYvGFf1OJHNczeY10/EFF8Ir8jZbNntQJxMWNfeQjC1mxPsaQ6mR9cvQbf+0YeMw==", + "requires": { + "@smithy/middleware-serde": "^3.0.3", + "@smithy/node-config-provider": "^3.1.4", + "@smithy/shared-ini-file-loader": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/url-parser": "^3.0.3", + "@smithy/util-middleware": "^3.0.3", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/middleware-stack": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", + "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/node-http-handler": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz", + "integrity": "sha512-+UmxgixgOr/yLsUxcEKGH0fMNVteJFGkmRltYFHnBMlogyFdpzn2CwqWmxOrfJELhV34v0WSlaqG1UtE1uXlJg==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/protocol-http": "^4.1.0", + "@smithy/querystring-builder": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/protocol-http": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", + "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-builder": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz", + "integrity": "sha512-vyWckeUeesFKzCDaRwWLUA1Xym9McaA6XpFfAK5qI9DKJ4M33ooQGqvM4J+LalH4u/Dq9nFiC8U6Qn1qi0+9zw==", + "requires": { + "@smithy/types": "^3.3.0", + "@smithy/util-uri-escape": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/querystring-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz", + "integrity": "sha512-zahM1lQv2YjmznnfQsWbYojFe55l0SLG/988brlLv1i8z3dubloLF+75ATRsqPBboUXsW6I9CPGE5rQgLfY0vQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/smithy-client": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", + "integrity": "sha512-pDbtxs8WOhJLJSeaF/eAbPgXg4VVYFlRcL/zoNYA5WbG3wBL06CHtBSg53ppkttDpAJ/hdiede+xApip1CwSLw==", + "requires": { + "@smithy/middleware-endpoint": "^3.1.0", + "@smithy/middleware-stack": "^3.0.3", + "@smithy/protocol-http": "^4.1.0", + "@smithy/types": "^3.3.0", + "@smithy/util-stream": "^3.1.3", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/url-parser": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-3.0.3.tgz", + "integrity": "sha512-pw3VtZtX2rg+s6HMs6/+u9+hu6oY6U7IohGhVNnjbgKy86wcIsSZwgHrFR+t67Uyxvp4Xz3p3kGXXIpTNisq8A==", + "requires": { + "@smithy/querystring-parser": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-base64": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-3.0.0.tgz", + "integrity": "sha512-Kxvoh5Qtt0CDsfajiZOCpJxgtPHXOKwmM+Zy4waD43UoEMA+qPxxa98aE/7ZhdnBFZFXMOiBR5xbcaMhLtznQQ==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-buffer-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-3.0.0.tgz", + "integrity": "sha512-aEOHCgq5RWFbP+UDPvPot26EJHjOC+bRgse5A8V3FSShqd5E5UN4qc7zkwsvJPPAVsf73QwYcHN1/gt/rtLwQA==", + "requires": { + "@smithy/is-array-buffer": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-hex-encoding": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz", + "integrity": "sha512-eFndh1WEK5YMUYvy3lPlVmYY/fZcQE1D8oSf41Id2vCeIkKJXPcYDCZD+4+xViI6b1XSd7tE+s5AmXzz5ilabQ==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-middleware": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", + "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-stream": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-3.1.3.tgz", + "integrity": "sha512-FIv/bRhIlAxC0U7xM1BCnF2aDRPq0UaelqBHkM2lsCp26mcBbgI0tCVTv+jGdsQLUmAMybua/bjDsSu8RQHbmw==", + "requires": { + "@smithy/fetch-http-handler": "^3.2.4", + "@smithy/node-http-handler": "^3.1.4", + "@smithy/types": "^3.3.0", + "@smithy/util-base64": "^3.0.0", + "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "@smithy/util-uri-escape": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", + "integrity": "sha512-LqR7qYLgZTD7nWLBecUi4aqolw8Mhza9ArpNEQ881MJJIU2sE5iHCK6TdyqqzcDLy0OPe10IY4T8ctVdtynubg==", + "requires": { + "tslib": "^2.6.2" + } + }, + "@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "requires": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-endpoints": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-2.0.5.tgz", + "integrity": "sha512-ReQP0BWihIE68OAblC/WQmDD40Gx+QY1Ez8mTdFMXpmjfxSyz2fVQu3A4zXRfQU9sZXtewk3GmhfOHswvX+eNg==", + "requires": { + "@smithy/node-config-provider": "^3.1.4", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-hex-encoding": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.2.tgz", + "integrity": "sha512-Bxydb5rMJorMV6AuDDMOxro3BMDdIwtbQKHpwvQFASkmr52BnpDsWlxgpJi8Iq7nk1Bt4E40oE1Isy/7ubHGzg==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-middleware": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-1.0.2.tgz", + "integrity": "sha512-vtXK7GOR2BoseCX8NCGe9SaiZrm9M2lm/RVexFGyPuafTtry9Vyv7hq/vw8ifd/G/pSJ+msByfJVb1642oQHKw==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-retry": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-3.0.3.tgz", + "integrity": "sha512-AFw+hjpbtVApzpNDhbjNG5NA3kyoMs7vx0gsgmlJF4s+yz1Zlepde7J58zpIRIsdjc+emhpAITxA88qLkPF26w==", + "requires": { + "@smithy/service-error-classification": "^3.0.3", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "@smithy/util-stream": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-1.0.2.tgz", + "integrity": "sha512-qyN2M9QFMTz4UCHi6GnBfLOGYKxQZD01Ga6nzaXFFC51HP/QmArU72e4kY50Z/EtW8binPxspP2TAsGbwy9l3A==", + "requires": { + "@smithy/fetch-http-handler": "^1.0.2", + "@smithy/node-http-handler": "^1.0.3", + "@smithy/types": "^1.1.1", + "@smithy/util-base64": "^1.0.2", + "@smithy/util-buffer-from": "^1.0.2", + "@smithy/util-hex-encoding": "^1.0.2", + "@smithy/util-utf8": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/util-uri-escape": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-1.0.2.tgz", + "integrity": "sha512-k8C0BFNS9HpBMHSgUDnWb1JlCQcFG+PPlVBq9keP4Nfwv6a9Q0yAfASWqUCtzjuMj1hXeLhn/5ADP6JxnID1Pg==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-utf8": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-1.0.2.tgz", + "integrity": "sha512-V4cyjKfJlARui0dMBfWJMQAmJzoW77i4N3EjkH/bwnE2Ngbl4tqD2Y0C/xzpzY/J1BdxeCKxAebVFk8aFCaSCw==", + "requires": { + "@smithy/util-buffer-from": "^1.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/util-waiter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-3.1.2.tgz", + "integrity": "sha512-4pP0EV3iTsexDx+8PPGAKCQpd/6hsQBaQhqWzU4hqKPHN5epPsxKbvUTIiYIHTxaKt6/kEaqPBpu/ufvfbrRzw==", + "requires": { + "@smithy/abort-controller": "^3.1.1", + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-3.1.1.tgz", + "integrity": "sha512-MBJBiidoe+0cTFhyxT8g+9g7CeVccLM0IOKKUMCNQ1CNMJ/eIfoo0RTfVrXOONEI1UCN1W+zkiHSbzUNE9dZtQ==", + "requires": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + } + }, + "@smithy/types": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", + "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "requires": { + "tslib": "^2.6.2" + } + }, + "tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + } + } + }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "amqplib": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/amqplib/-/amqplib-0.8.0.tgz", + "integrity": "sha512-icU+a4kkq4Y1PS4NNi+YPDMwdlbFcZ1EZTQT2nigW3fvOb6AOgUQ9+Mk4ue0Zu5cBg/XpDzB40oH10ysrk2dmA==", + "requires": { + "bitsyntax": "~0.1.0", + "bluebird": "^3.7.2", + "buffer-more-ints": "~1.0.0", + "readable-stream": "1.x >=1.1.9", + "safe-buffer": "~5.2.1", + "url-parse": "~1.5.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bitsyntax": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bitsyntax/-/bitsyntax-0.1.0.tgz", + "integrity": "sha512-ikAdCnrloKmFOugAfxWws89/fPc+nw0OOG1IzIE72uSOg/A3cYptKCjSUhDTuj7fhsJtzkzlv7l3b8PzRHLN0Q==", + "requires": { + "buffer-more-ints": "~1.0.0", + "debug": "~2.6.9", + "safe-buffer": "~5.1.2" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "bowser": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", + "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "buffer-more-ints": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz", + "integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "combined-stream": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "config": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/config/-/config-2.0.1.tgz", + "integrity": "sha512-aTaviJnC8ZjQYx8kQf4u6tWqIxWolyQQ3LqXgnCLAsIb78JrUshHG0YuzIarzTaVVe1Pazms3TXImfYra8UsyQ==", + "requires": { + "json5": "^1.0.1" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", + "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==" + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dnscache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dnscache/-/dnscache-1.0.1.tgz", + "integrity": "sha1-Qssrm/tej736OVqsdOEn/AUHTTE=", + "requires": { + "asap": "~2.0.3", + "lodash.clone": "~4.3.2" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "1.1.0", + "resolved": "http://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "requires": { + "strnum": "^1.0.5" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.1.tgz", + "integrity": "sha1-+kt5+kf9Pe9eOxWYJRYcClGclCc=" + }, + "is-electron": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.1.tgz", + "integrity": "sha512-r8EEQQsqT+Gn0aXFx7lTFygYQhILLCB+wn0WCDL5LZRINeLH/Rvw1j2oKodELLXYNImQ3CRlVsY8wW4cGOsyuw==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + } + }, + "jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "dependencies": { + "json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + } + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash._baseclone": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-4.5.7.tgz", + "integrity": "sha1-zkKt4IOE711i+nfDD2GkbmhvhDQ=" + }, + "lodash.clone": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.3.2.tgz", + "integrity": "sha1-5WsXa2gjp93jj38r9Y3n1ZcSAOk=", + "requires": { + "lodash._baseclone": "~4.5.0" + } + }, + "log4js": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", + "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", + "requires": { + "date-format": "^4.0.3", + "debug": "^4.3.3", + "flatted": "^3.2.4", + "rfdc": "^1.3.0", + "streamroller": "^3.0.2" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + } + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node-cache": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.1.tgz", + "integrity": "sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==", + "requires": { + "clone": "2.x", + "lodash": "^4.17.15" + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, + "node-statsd": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/node-statsd/-/node-statsd-0.1.1.tgz", + "integrity": "sha1-J6WTSHY9CvegN6wqAx/vPwUQE9M=" + }, + "nodemailer": { + "version": "6.9.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.13.tgz", + "integrity": "sha512-7o38Yogx6krdoBf3jCAqnIN4oSQFx+fMa0I7dK1D+me9kBxx12D+/33wSb+fhOCtIxvYJ+4x4IMEhmhCKfAiOA==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, + "request-filtering-agent": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/request-filtering-agent/-/request-filtering-agent-1.0.5.tgz", + "integrity": "sha512-zkO5wg1VhoPqz5FIIXLY+iEveiX+p6A/i++SS4ZXd0UDms6VtHLLYLw9hQnIbMsKbYwHgzhNeIis7jMncoG9ag==", + "requires": { + "ipaddr.js": "^1.9.1" + }, + "dependencies": { + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + } + } + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, + "rhea": { + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/rhea/-/rhea-1.0.24.tgz", + "integrity": "sha512-PEl62U2EhxCO5wMUZ2/bCBcXAVKN9AdMSNQOrp3+R5b77TEaOSiy16MQ0sIOmzj/iqsgIAgPs1mt3FYfu1vIXA==", + "requires": { + "debug": "0.8.0 - 3.5.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "requires": { + "through": "2" + } + }, + "sshpk": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz", + "integrity": "sha512-Ra/OXQtuh0/enyl4ETZAfTaeksa6BXks5ZcjpSUNrjBr0DvrJKX+1fsKDPpT9TBXgHAFsa4510aNVgI8g/+SzA==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "streamroller": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", + "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", + "requires": { + "date-format": "^4.0.3", + "debug": "^4.1.1", + "fs-extra": "^10.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", + "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", + "requires": { + "ms": "2.1.2" + } + }, + "fs-extra": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", + "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "win-ca": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/win-ca/-/win-ca-3.5.0.tgz", + "integrity": "sha512-0TgO/+2iz2pS3OxBy2ikovPHOYyZRdLRxRTT9ze7DpZwEpaahLFOBuac93GM3lYEVzDyf8fXskJjIX/EILvkhQ==", + "requires": { + "is-electron": "^2.2.0", + "make-dir": "^1.3.0", + "node-forge": "^1.2.1", + "split": "^1.0.1" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/Common/package.json b/Common/package.json index 075efcad2e..85f536c3f9 100644 --- a/Common/package.json +++ b/Common/package.json @@ -1,26 +1,31 @@ { "name": "common", "version": "1.0.1", - "homepage": "http://www.onlyoffice.com", + "homepage": "https://www.onlyoffice.com", "private": true, "dependencies": { - "amazon-s3-url-signer": "https://github.com/agolybev/amazon-s3-url-signer/archive/v0.0.9.tar.gz", - "amqplib": "^0.4.1", - "aws-sdk": "^2.4.12", - "co": "^4.6.0", - "config": "^1.21.0", - "dnscache": "0.0.4", - "escape-string-regexp": "^1.0.5", - "forwarded": "^0.1.0", - "ipaddr.js": "^1.2.0", - "jsonwebtoken": "^7.1.9", - "log4js": "^0.6.38", - "mime": "^1.3.4", - "mkdirp": "^0.5.1", - "ms": "^0.7.2", - "node-cache": "^4.1.0", - "node-statsd": "^0.1.1", - "request": "^2.74.0", - "uri-js": "^3.0.2" + "@aws-sdk/client-s3": "3.637.0", + "@aws-sdk/node-http-handler": "3.374.0", + "@aws-sdk/s3-request-presigner": "3.370.0", + "amqplib": "0.8.0", + "co": "4.6.0", + "config": "2.0.1", + "content-disposition": "0.5.3", + "dnscache": "1.0.1", + "escape-string-regexp": "1.0.5", + "forwarded": "0.1.2", + "ipaddr.js": "1.8.1", + "jsonwebtoken": "9.0.0", + "log4js": "6.4.1", + "mime": "2.3.1", + "ms": "2.1.1", + "node-cache": "4.2.1", + "node-statsd": "0.1.1", + "nodemailer": "6.9.13", + "request": "2.88.0", + "request-filtering-agent": "1.0.5", + "rhea": "1.0.24", + "uri-js": "4.2.2", + "win-ca": "3.5.0" } } diff --git a/Common/sources/activeMQCore.js b/Common/sources/activeMQCore.js new file mode 100644 index 0000000000..d8353435f7 --- /dev/null +++ b/Common/sources/activeMQCore.js @@ -0,0 +1,104 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; +var config = require('config'); +var container = require('rhea'); +var logger = require('./logger'); +const operationContext = require('./operationContext'); + +const cfgRabbitSocketOptions = config.get('activemq.connectOptions'); + +var RECONNECT_TIMEOUT = 1000; + +function connetPromise(closeCallback) { + return new Promise(function(resolve, reject) { + //todo use built-in reconnect logic + function startConnect() { + let onDisconnected = function() { + if (isConnected) { + closeCallback(); + } else { + setTimeout(startConnect, RECONNECT_TIMEOUT); + } + } + let conn = container.create_container().connect(cfgRabbitSocketOptions); + let isConnected = false; + conn.on('connection_open', function(context) { + operationContext.global.logger.debug('[AMQP] connected'); + isConnected = true; + resolve(conn); + }); + conn.on('connection_error', function(context) { + operationContext.global.logger.debug('[AMQP] connection_error %s', context.error && context.error); + }); + conn.on('connection_close', function(context) { + operationContext.global.logger.debug('[AMQP] conn close'); + if (onDisconnected) { + onDisconnected(); + onDisconnected = null; + } + }); + conn.on('disconnected', function(context) { + operationContext.global.logger.error('[AMQP] disconnected %s', context.error && context.error.stack); + if (onDisconnected) { + onDisconnected(); + onDisconnected = null; + } + }); + } + + startConnect(); + }); +} +function openSenderPromise(conn, options) { + return new Promise(function(resolve, reject) { + resolve(conn.open_sender(options)); + }); +} +function openReceiverPromise(conn, options) { + return new Promise(function(resolve, reject) { + resolve(conn.open_receiver(options)); + }); +} +function closePromise(conn) { + return new Promise(function(resolve, reject) { + conn.close(); + resolve(); + }); +} + +module.exports.connetPromise = connetPromise; +module.exports.openSenderPromise = openSenderPromise; +module.exports.openReceiverPromise = openReceiverPromise; +module.exports.closePromise = closePromise; +module.exports.RECONNECT_TIMEOUT = RECONNECT_TIMEOUT; diff --git a/Common/sources/commondefines.js b/Common/sources/commondefines.js index ba89aecfdb..588734e646 100644 --- a/Common/sources/commondefines.js +++ b/Common/sources/commondefines.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,14 +32,24 @@ 'use strict'; +const config = require("config"); const constants = require('./constants'); -function InputCommand(data) { +function InputCommand(data, copyExplicit) { + //must be set explicitly to prevent vulnerability(downloadAs(with url) creates request to integrator with authorization) + this['withAuthorization'] = undefined;//bool + this['externalChangeInfo'] = undefined;//zero DB changes case: set password, undo all changes + this['wopiParams'] = undefined; + this['builderParams'] = undefined; + this['userconnectiondocid'] = undefined; if (data) { this['c'] = data['c']; this['id'] = data['id']; this['userid'] = data['userid']; - this['jwt'] = data['jwt']; + this['userindex'] = data['userindex']; + this['username'] = data['username']; + this['tokenSession'] = data['tokenSession']; + this['tokenDownload'] = data['tokenDownload']; this['data'] = data['data']; this['editorid'] = data['editorid']; this['format'] = data['format']; @@ -51,6 +61,7 @@ function InputCommand(data) { this['saveindex'] = data['saveindex']; this['codepage'] = data['codepage']; this['delimiter'] = data['delimiter']; + this['delimiterChar'] = data['delimiterChar']; this['embeddedfonts'] = data['embeddedfonts']; if (data['mailmergesend']) { this['mailmergesend'] = new CMailMergeSendData(data['mailmergesend']); @@ -62,29 +73,57 @@ function InputCommand(data) { } else { this['thumbnail'] = undefined; } + if (data['textParams']) { + this['textParams'] = new CTextParams(data['textParams']); + } else { + this['textParams'] = undefined; + } this['status'] = data['status']; this['status_info'] = data['status_info']; this['savekey'] = data['savekey']; this['userconnectionid'] = data['userconnectionid']; - this['docconnectionid'] = data['docconnectionid']; - this['doctparams'] = data['doctparams']; + this['responsekey'] = data['responsekey']; + this['jsonparams'] = data['jsonparams']; + this['lcid'] = data['lcid']; this['useractionid'] = data['useractionid']; + this['useractionindex'] = data['useractionindex']; if (data['forcesave']) { this['forcesave'] = new CForceSaveData(data['forcesave']); } else { this['forcesave'] = undefined; } this['userdata'] = data['userdata']; + this['formdata'] = data['formdata']; this['inline'] = data['inline']; this['password'] = data['password']; + this['savepassword'] = data['savepassword']; + this['withoutPassword'] = data['withoutPassword']; this['outputurls'] = data['outputurls']; - this['closeonerror'] = data['closeonerror']; + this['serverVersion'] = data['serverVersion']; this['rediskey'] = data['rediskey']; + this['nobase64'] = data['nobase64']; + this['forgotten'] = data['forgotten']; + this['status_info_in'] = data['status_info_in']; + this['attempt'] = data['attempt']; + this['convertToOrigin'] = data['convertToOrigin']; + this['isSaveAs'] = data['isSaveAs']; + this['saveAsPath'] = data['saveAsPath']; + if (copyExplicit) { + this['withAuthorization'] = data['withAuthorization']; + this['externalChangeInfo'] = data['externalChangeInfo']; + this['wopiParams'] = data['wopiParams']; + this['builderParams'] = data['builderParams']; + this['userconnectiondocid'] = data['userconnectiondocid']; + this['originformat'] = data['originformat']; + } } else { this['c'] = undefined;//string command this['id'] = undefined;//string document id this['userid'] = undefined;//string - this['jwt'] = undefined;//string validate + this['userindex'] = undefined; + this['username'] = undefined; + this['tokenSession'] = undefined;//string validate + this['tokenDownload'] = undefined;//string validate this['data'] = undefined;//string //to open this['editorid'] = undefined;//int @@ -99,6 +138,7 @@ function InputCommand(data) { //nullable this['codepage'] = undefined; this['delimiter'] = undefined; + this['delimiterChar'] = undefined; this['embeddedfonts'] = undefined;//bool this['mailmergesend'] = undefined; this['thumbnail'] = undefined; @@ -107,19 +147,35 @@ function InputCommand(data) { this['status_info'] = undefined;//int this['savekey'] = undefined;//int document id to save this['userconnectionid'] = undefined;//string internal - this['docconnectionid'] = undefined;//string internal - this['doctparams'] = undefined;//int doctRenderer + this['responsekey'] = undefined; + this['jsonparams'] = undefined;//string + this['lcid'] = undefined; this['useractionid'] = undefined; + this['useractionindex'] = undefined; this['forcesave'] = undefined; this['userdata'] = undefined; + this['formdata'] = undefined; this['inline'] = undefined;//content disposition this['password'] = undefined; + this['savepassword'] = undefined; + this['withoutPassword'] = undefined; this['outputurls'] = undefined; - this['closeonerror'] = undefined; + this['serverVersion'] = undefined; this['rediskey'] = undefined; + this['nobase64'] = true; + this['forgotten'] = undefined; + this['status_info_in'] = undefined; + this['attempt'] = undefined; + this['convertToOrigin'] = undefined; + this['originformat'] = undefined; + this['isSaveAs'] = undefined; + this['saveAsPath'] = undefined; } } InputCommand.prototype = { + fillFromConnection: function(conn) { + this['id'] = conn.docId; + }, getCommand: function() { return this['c']; }, @@ -138,8 +194,23 @@ InputCommand.prototype = { setUserId: function(data) { this['userid'] = data; }, - getJwt: function() { - return this['jwt']; + getUserIndex: function() { + return this['userindex']; + }, + setUserIndex: function(data) { + this['userindex'] = data; + }, + getUserName: function() { + return this['username']; + }, + setUserName: function(data) { + this['username'] = data; + }, + getTokenSession: function() { + return this['tokenSession']; + }, + getTokenDownload: function() { + return this['tokenDownload']; }, getData: function() { return this['data']; @@ -153,6 +224,12 @@ InputCommand.prototype = { setFormat: function(data) { this['format'] = data; }, + getOriginFormat: function() { + return this['originformat']; + }, + setOriginFormat: function(data) { + this['originformat'] = data; + }, getUrl: function() { return this['url']; }, @@ -201,6 +278,12 @@ InputCommand.prototype = { setDelimiter: function(data) { this['delimiter'] = data; }, + getDelimiterChar: function() { + return this['delimiterChar']; + }, + setDelimiterChar: function(data) { + this['delimiterChar'] = data; + }, getEmbeddedFonts: function() { return this['embeddedfonts']; }, @@ -219,6 +302,12 @@ InputCommand.prototype = { setThumbnail: function(data) { this['thumbnail'] = data; }, + getTextParams: function() { + return this['textParams']; + }, + setTextParams: function(data) { + this['textParams'] = data; + }, getStatus: function() { return this['status']; }, @@ -237,23 +326,45 @@ InputCommand.prototype = { setSaveKey: function(data) { this['savekey'] = data; }, + getForgotten: function() { + return this['forgotten']; + }, + setForgotten: function(data) { + this['forgotten'] = data; + }, getUserConnectionId: function() { return this['userconnectionid']; }, setUserConnectionId: function(data) { this['userconnectionid'] = data; }, - getDocConnectionId: function() { - return this['docconnectionid']; + getUserConnectionDocId: function() { + return this['userconnectiondocid']; + }, + setUserConnectionDocId: function(data) { + this['userconnectiondocid'] = data; + }, + getResponseKey: function() { + return this['responsekey']; + }, + setResponseKey: function(data) { + this['responsekey'] = data; }, - setDocConnectionId: function(data) { - this['docconnectionid'] = data; + getJsonParams: function() { + return this['jsonparams']; }, - getDoctParams: function() { - return this['doctparams']; + appendJsonParams: function (data) { + if (this['jsonparams']) { + config.util.extendDeep(this['jsonparams'], data); + } else { + this['jsonparams'] = data; + } + }, + getLCID: function() { + return this['lcid']; }, - setDoctParams: function(data) { - this['doctparams'] = data; + setLCID: function(data) { + this['lcid'] = data; }, getUserActionId: function() { return this['useractionid']; @@ -261,9 +372,21 @@ InputCommand.prototype = { setUserActionId: function(data) { this['useractionid'] = data; }, + getUserActionIndex: function() { + return this['useractionindex']; + }, + setUserActionIndex: function(data) { + this['useractionindex'] = data; + }, + /** + * @return {CForceSaveData | null} + */ getForceSave: function() { return this['forcesave']; }, + /** + * @param {CForceSaveData} data + */ setForceSave: function(data) { this['forcesave'] = data; }, @@ -273,6 +396,12 @@ InputCommand.prototype = { setUserData: function(data) { this['userdata'] = data; }, + getFormData: function() { + return this['formdata']; + }, + setFormData: function(data) { + this['formdata'] = data; + }, getInline: function() { return this['inline']; }, @@ -285,23 +414,95 @@ InputCommand.prototype = { setPassword: function(data) { this['password'] = data; }, + getSavePassword: function() { + return this['savepassword']; + }, + setSavePassword: function(data) { + this['savepassword'] = data; + }, + getWithoutPassword: function() { + return this['withoutPassword']; + }, + setWithoutPassword: function(data) { + this['withoutPassword'] = data; + }, setOutputUrls: function(data) { this['outputurls'] = data; }, getOutputUrls: function() { return this['outputurls']; }, - getCloseOnError: function() { - return this['closeonerror']; + getServerVersion: function() { + return this['serverVersion']; }, - setCloseOnError: function(data) { - this['closeonerror'] = data; + setServerVersion: function(data) { + this['serverVersion'] = data; }, getRedisKey: function() { return this['rediskey']; }, setRedisKey: function(data) { this['rediskey'] = data; + }, + getNoBase64: function() { + return this['nobase64']; + }, + setNoBase64: function(data) { + this['nobase64'] = data; + }, + getStatusInfoIn: function() { + return this['status_info_in']; + }, + setStatusInfoIn: function(data) { + this['status_info_in'] = data; + }, + getAttempt: function() { + return this['attempt']; + }, + setAttempt: function(data) { + this['attempt'] = data; + }, + getWithAuthorization: function() { + return this['withAuthorization']; + }, + setWithAuthorization: function(data) { + this['withAuthorization'] = data; + }, + getExternalChangeInfo: function() { + return this['externalChangeInfo']; + }, + setExternalChangeInfo: function(data) { + this['externalChangeInfo'] = data; + }, + getBuilderParams: function() { + return this['builderParams']; + }, + setBuilderParams: function(data) { + this['builderParams'] = data; + }, + getWopiParams: function() { + return this['wopiParams']; + }, + setWopiParams: function(data) { + this['wopiParams'] = data; + }, + getConvertToOrigin: function() { + return this['convertToOrigin']; + }, + setConvertToOrigin: function(data) { + this['convertToOrigin'] = data; + }, + getIsSaveAs: function() { + return this['isSaveAs']; + }, + setIsSaveAs: function(data) { + this['isSaveAs'] = data; + }, + getSaveAsPath: function() { + return this['saveAsPath']; + }, + setSaveAsPath: function(data) { + this['saveAsPath'] = data; } }; @@ -310,10 +511,14 @@ function CForceSaveData(obj) { this['type'] = obj['type']; this['time'] = obj['time']; this['index'] = obj['index']; + this['authoruserid'] = obj['authoruserid']; + this['authoruserindex'] = obj['authoruserindex']; } else { this['type'] = null; this['time'] = null; this['index'] = null; + this['authoruserid'] = null; + this['authoruserindex'] = null; } } CForceSaveData.prototype.getType = function() { @@ -334,6 +539,18 @@ CForceSaveData.prototype.getIndex = function() { CForceSaveData.prototype.setIndex = function(v) { this['index'] = v; }; +CForceSaveData.prototype.getAuthorUserId = function() { + return this['authoruserid']; +}; +CForceSaveData.prototype.setAuthorUserId = function(v) { + this['authoruserid'] = v; +}; +CForceSaveData.prototype.getAuthorUserIndex = function() { + return this['authoruserindex']; +}; +CForceSaveData.prototype.setAuthorUserIndex = function(v) { + this['authoruserindex'] = v; +}; function CThumbnailData(obj) { if (obj) { @@ -380,6 +597,19 @@ CThumbnailData.prototype.getHeight = function() { CThumbnailData.prototype.setHeight = function(v) { this['height'] = v; }; +function CTextParams(obj) { + if (obj) { + this['association'] = obj['association']; + } else { + this['association'] = null; + } +} +CTextParams.prototype.getAssociation = function() { + return this['association'] +}; +CTextParams.prototype.setAssociation = function(v) { + this['association'] = v; +}; function CMailMergeSendData(obj) { if (obj) { @@ -397,6 +627,7 @@ function CMailMergeSendData(obj) { this['url'] = obj['url']; this['baseUrl'] = obj['baseUrl']; this['jsonkey'] = obj['jsonkey']; + this['isJson'] = obj['isJson']; } else { this['from'] = null; this['to'] = null; @@ -412,6 +643,7 @@ function CMailMergeSendData(obj) { this['url'] = null; this['baseUrl'] = null; this['jsonkey'] = null; + this['isJson'] = null; } } CMailMergeSendData.prototype.getFrom = function() { @@ -498,9 +730,16 @@ CMailMergeSendData.prototype.getJsonKey = function() { CMailMergeSendData.prototype.setJsonKey = function(v) { this['jsonkey'] = v; }; +CMailMergeSendData.prototype.getIsJsonKey = function() { + return this['isJson'] +}; +CMailMergeSendData.prototype.setIsJsonKey = function(v) { + this['isJson'] = v; +}; function TaskQueueData(data) { if (data) { - this['cmd'] = new InputCommand(data['cmd']); + this['ctx'] = data['ctx']; + this['cmd'] = new InputCommand(data['cmd'], true); this['toFile'] = data['toFile']; this['fromOrigin'] = data['fromOrigin']; this['fromSettings'] = data['fromSettings']; @@ -510,6 +749,7 @@ function TaskQueueData(data) { this['dataKey'] = data['dataKey']; this['visibilityTimeout'] = data['visibilityTimeout']; } else { + this['ctx'] = undefined; this['cmd'] = undefined; this['toFile'] = undefined; this['fromOrigin'] = undefined; @@ -522,6 +762,12 @@ function TaskQueueData(data) { } } TaskQueueData.prototype = { + getCtx : function() { + return this['ctx']; + }, + setCtx : function(data) { + return this['ctx'] = data; + }, getCmd : function() { return this['cmd']; }, @@ -572,8 +818,8 @@ TaskQueueData.prototype = { } }; -function OutputSfcData() { - this['key'] = undefined; +function OutputSfcData(key) { + this['key'] = key; this['status'] = undefined; this['url'] = undefined; this['changesurl'] = undefined; @@ -582,9 +828,13 @@ function OutputSfcData() { this['actions'] = undefined; this['mailMerge'] = undefined; this['userdata'] = undefined; + this['formdata'] = undefined; this['lastsave'] = undefined; this['notmodified'] = undefined; this['forcesavetype'] = undefined; + this['encrypted'] = undefined; + + this['token'] = undefined; } OutputSfcData.prototype.getKey = function() { return this['key']; @@ -604,6 +854,12 @@ OutputSfcData.prototype.getUrl = function() { OutputSfcData.prototype.setUrl = function(data) { return this['url'] = data; }; +OutputSfcData.prototype.getExtName = function() { + return this['filetype']; +}; +OutputSfcData.prototype.setExtName = function(data) { + return this['filetype'] = data.substring(1); +}; OutputSfcData.prototype.getChangeUrl = function() { return this['changesurl']; }; @@ -640,6 +896,12 @@ OutputSfcData.prototype.getUserData= function() { OutputSfcData.prototype.setUserData = function(data) { return this['userdata'] = data; }; +OutputSfcData.prototype.getFormsDataUrl= function() { + return this['formsdataurl']; +}; +OutputSfcData.prototype.setFormsDataUrl = function(data) { + return this['formsdataurl'] = data; +}; OutputSfcData.prototype.getLastSave = function() { return this['lastsave'] }; @@ -658,6 +920,18 @@ OutputSfcData.prototype.getForceSaveType = function() { OutputSfcData.prototype.setForceSaveType = function(v) { this['forcesavetype'] = v; }; +OutputSfcData.prototype.getEncrypted = function() { + return this['encrypted'] +}; +OutputSfcData.prototype.setEncrypted = function(v) { + this['encrypted'] = v; +}; +OutputSfcData.prototype.getToken = function() { + return this['token'] +}; +OutputSfcData.prototype.setToken = function(v) { + this['token'] = v; +}; function OutputMailMerge(mailMergeSendData) { if (mailMergeSendData) { @@ -667,7 +941,7 @@ function OutputMailMerge(mailMergeSendData) { this['title'] = mailMergeSendData.getFileName(); const mailFormat = mailMergeSendData.getMailFormat(); switch (mailFormat) { - case constants.AVS_OFFICESTUDIO_FILE_OTHER_HTMLZIP : + case constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_HTML : this['type'] = 0; break; case constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX : @@ -718,6 +992,20 @@ function OutputAction(type, userid) { this['type'] = type; this['userid'] = userid; } + +function ConvertStatus(err, url, filetype) { + this.err = err; + this.url = url; + this.filetype = filetype; + this.end = !!url; +} +ConvertStatus.prototype.setExtName = function(extname) { + this.filetype = extname.substring(1); +}; +ConvertStatus.prototype.setUrl = function(url) { + this.url = url; + this.end = true; +}; const c_oPublishType = { drop : 0, releaseLock : 1, @@ -731,7 +1019,11 @@ const c_oPublishType = { cursor: 9, shutdown: 10, meta: 11, - forceSave: 12 + forceSave: 12, + closeConnection: 13, + changesNotify: 14, + changeConnecitonInfo: 15, + rpc: 16 }; const c_oAscCsvDelimiter = { None: 0, @@ -817,7 +1109,8 @@ const c_oAscEncodingsMap = {"437": 43, "720": 1, "737": 21, "775": 5, "850": 39, const c_oAscCodePageUtf8 = 46;//65001 const c_oAscUserAction = { Out: 0, - In: 1 + In: 1, + ForceSaveButton: 2 }; const c_oAscServerCommandErrors = { NoError: 0, @@ -832,7 +1125,39 @@ const c_oAscServerCommandErrors = { const c_oAscForceSaveTypes = { Command: 0, Button: 1, - Timeout: 2 + Timeout: 2, + Form: 3, + Internal: 4 +}; +const c_oAscUrlTypes = { + Session: 0, + Temporary: 1 +}; +const c_oAscSecretType = { + Browser: 0, + Inbox: 1, + Outbox: 2, + Session: 3 +}; +const c_oAscQueueType = { + rabbitmq: 'rabbitmq', + activemq: 'activemq' +}; +const c_oAscUnlockRes = { + Locked: 0, + Unlocked: 1, + Empty: 2 +}; +const FileStatus = { + None: 0, + Ok: 1, + WaitQueue: 2, + NeedParams: 3, + Err: 5, + ErrToReload: 6, + SaveVersion: 7, + UpdateVersion: 8, + NeedPassword: 9 }; const buildVersion = '4.1.2'; @@ -841,11 +1166,13 @@ const buildNumber = 37; exports.TaskQueueData = TaskQueueData; exports.CMailMergeSendData = CMailMergeSendData; exports.CThumbnailData = CThumbnailData; +exports.CTextParams = CTextParams; exports.CForceSaveData = CForceSaveData; exports.InputCommand = InputCommand; exports.OutputSfcData = OutputSfcData; exports.OutputMailMerge = OutputMailMerge; exports.OutputAction = OutputAction; +exports.ConvertStatus = ConvertStatus; exports.c_oPublishType = c_oPublishType; exports.c_oAscCsvDelimiter = c_oAscCsvDelimiter; exports.c_oAscEncodings = c_oAscEncodings; @@ -854,5 +1181,10 @@ exports.c_oAscCodePageUtf8 = c_oAscCodePageUtf8; exports.c_oAscUserAction = c_oAscUserAction; exports.c_oAscServerCommandErrors = c_oAscServerCommandErrors; exports.c_oAscForceSaveTypes = c_oAscForceSaveTypes; +exports.c_oAscUrlTypes = c_oAscUrlTypes; +exports.c_oAscSecretType = c_oAscSecretType; +exports.c_oAscQueueType = c_oAscQueueType; +exports.c_oAscUnlockRes = c_oAscUnlockRes; +exports.FileStatus = FileStatus; exports.buildVersion = buildVersion; exports.buildNumber = buildNumber; diff --git a/Common/sources/constants.js b/Common/sources/constants.js index 6003ac5314..4fc0d8078c 100644 --- a/Common/sources/constants.js +++ b/Common/sources/constants.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -33,10 +33,25 @@ 'use strict'; exports.DOC_ID_PATTERN = '0-9-.a-zA-Z_='; +exports.DOC_ID_REGEX = new RegExp("^[" + exports.DOC_ID_PATTERN + "]*$", 'i'); +exports.DOC_ID_REPLACE_REGEX = new RegExp("[^" + exports.DOC_ID_PATTERN + "]", 'g'); +exports.DOC_ID_SOCKET_PATTERN = new RegExp("^/doc/([" + exports.DOC_ID_PATTERN + "]*)/c.+", 'i'); +exports.DOC_ID_MAX_LENGTH = 240; +exports.USER_ID_MAX_LENGTH = 240;//255-240=15 symbols to make user id unique +exports.USER_NAME_MAX_LENGTH = 255; +exports.PASSWORD_MAX_LENGTH = 255;//set password limit for DoS protection with long password +exports.EXTENTION_REGEX = /^[a-zA-Z0-9]*$/; exports.CHAR_DELIMITER = String.fromCharCode(5); exports.OUTPUT_NAME = 'output'; exports.ONLY_OFFICE_URL_PARAM = 'ooname'; exports.DISPLAY_PREFIX = 'display'; +exports.CHANGES_NAME = 'changes'; +exports.VIEWER_ONLY = /^(?:(pdf|djvu|xps|oxps))$/; +exports.DEFAULT_DOC_ID = 'docId'; +exports.DEFAULT_USER_ID = 'userId'; +exports.ALLOWED_PROTO = /^https?$/i; +exports.SHARD_KEY_WOPI_NAME = 'WOPISrc'; +exports.SHARD_KEY_API_NAME = 'shardkey'; exports.RIGHTS = { None : 0, @@ -46,17 +61,35 @@ exports.RIGHTS = { View : 4 }; +exports.LICENSE_MODE = { + None: 0, + Trial: 1, + Developer: 2, + Limited: 4 +}; + exports.LICENSE_RESULT = { - Error : 1, - Expired : 2, - Success : 3, - UnknownUser : 4, - Connections : 5, - ExpiredTrial: 6, - SuccessLimit: 7 + Error : 1, + Expired : 2, + Success : 3, + UnknownUser : 4, + Connections : 5, + ExpiredTrial : 6, + SuccessLimit : 7, + UsersCount : 8, + ConnectionsOS : 9, + UsersCountOS : 10, + ExpiredLimited: 11, + ConnectionsLiveOS: 12, + ConnectionsLive: 13, + UsersViewCount: 14, + UsersViewCountOS: 15, + NotBefore: 16 }; -exports.LICENSE_CONNECTIONS = 21; +exports.LICENSE_CONNECTIONS = 20; +exports.LICENSE_USERS = 3; +exports.LICENSE_EXPIRE_USERS_ONE_DAY = 24 * 60 * 60; // day in seconds exports.AVS_OFFICESTUDIO_FILE_UNKNOWN = 0x0000; exports.AVS_OFFICESTUDIO_FILE_DOCUMENT = 0x0040; @@ -70,16 +103,47 @@ exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_MHT = exports.AVS_OFFICESTUDIO_FILE_DOCUM exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_EPUB = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0008; exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_FB2 = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0009; exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_MOBI = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x000a; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCM = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x000b; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOTX = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x000c; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOTM = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x000d; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_ODT_FLAT = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x000e; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_OTT = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x000f; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOC_FLAT = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0010; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX_FLAT = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0011; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_HTML_IN_CONTAINER = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0012; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX_PACKAGE = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0014; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0015; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0016; +exports.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF = exports.AVS_OFFICESTUDIO_FILE_DOCUMENT + 0x0017; + exports.AVS_OFFICESTUDIO_FILE_PRESENTATION = 0x0080; exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0001; exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPT = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0002; exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_ODP = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0003; exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPSX = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0004; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTM = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0005; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPSM = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0006; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_POTX = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0007; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_POTM = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0008; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_ODP_FLAT = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x0009; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_OTP = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x000a; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX_PACKAGE = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x000b; +exports.AVS_OFFICESTUDIO_FILE_PRESENTATION_ODG = exports.AVS_OFFICESTUDIO_FILE_PRESENTATION + 0x000c; + exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET = 0x0100; exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0001; exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLS = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0002; exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_ODS = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0003; exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_CSV = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0004; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSM = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0005; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLTX = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0006; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLTM = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0007; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSB = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0008; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_ODS_FLAT = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x0009; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_OTS = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x000a; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX_FLAT = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x000b; +exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX_PACKAGE = exports.AVS_OFFICESTUDIO_FILE_SPREADSHEET + 0x000c; + exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM = 0x0200; exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF = exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM + 0x0001; exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_SWF = exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM + 0x0002; @@ -89,6 +153,8 @@ exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_SVG = exports.AVS_OFFICESTUDIO_FILE_ exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_HTMLR = exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM + 0x0006; exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_HTMLR_MENU = exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM + 0x0007; exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_HTMLR_CANVAS = exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM + 0x0008; +exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA = exports.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM + 0x0009; + exports.AVS_OFFICESTUDIO_FILE_IMAGE = 0x0400; exports.AVS_OFFICESTUDIO_FILE_IMAGE_JPG = exports.AVS_OFFICESTUDIO_FILE_IMAGE + 0x0001; exports.AVS_OFFICESTUDIO_FILE_IMAGE_TIFF = exports.AVS_OFFICESTUDIO_FILE_IMAGE + 0x0002; @@ -103,6 +169,7 @@ exports.AVS_OFFICESTUDIO_FILE_IMAGE_PCX = exports.AVS_OFFICESTUDIO_FILE_IMAGE + exports.AVS_OFFICESTUDIO_FILE_IMAGE_RAS = exports.AVS_OFFICESTUDIO_FILE_IMAGE + 0x000b; exports.AVS_OFFICESTUDIO_FILE_IMAGE_PSD = exports.AVS_OFFICESTUDIO_FILE_IMAGE + 0x000c; exports.AVS_OFFICESTUDIO_FILE_IMAGE_ICO = exports.AVS_OFFICESTUDIO_FILE_IMAGE + 0x000d; + exports.AVS_OFFICESTUDIO_FILE_OTHER = 0x0800; exports.AVS_OFFICESTUDIO_FILE_OTHER_EXTRACT_IMAGE = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0001; exports.AVS_OFFICESTUDIO_FILE_OTHER_MS_OFFCRYPTO = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0002; @@ -110,21 +177,37 @@ exports.AVS_OFFICESTUDIO_FILE_OTHER_HTMLZIP = exports.AVS_OFFICESTUDIO_FILE_OTHE exports.AVS_OFFICESTUDIO_FILE_OTHER_OLD_DOCUMENT = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0004; exports.AVS_OFFICESTUDIO_FILE_OTHER_OLD_PRESENTATION = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0005; exports.AVS_OFFICESTUDIO_FILE_OTHER_OLD_DRAWING = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0006; -exports.AVS_OFFICESTUDIO_FILE_OTHER_TEAMLAB_INNER = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0007; +exports.AVS_OFFICESTUDIO_FILE_OTHER_OOXML = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0007; exports.AVS_OFFICESTUDIO_FILE_OTHER_JSON = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x0008; // Ð”Ð»Ñ mail-merge +exports.AVS_OFFICESTUDIO_FILE_OTHER_ODF = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x000a; +exports.AVS_OFFICESTUDIO_FILE_OTHER_MS_MITCRYPTO = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x000b; +exports.AVS_OFFICESTUDIO_FILE_OTHER_MS_VBAPROJECT = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x000c; +exports.AVS_OFFICESTUDIO_FILE_OTHER_PACKAGE_IN_OLE = exports.AVS_OFFICESTUDIO_FILE_OTHER + 0x000d; + exports.AVS_OFFICESTUDIO_FILE_TEAMLAB = 0x1000; exports.AVS_OFFICESTUDIO_FILE_TEAMLAB_DOCY = exports.AVS_OFFICESTUDIO_FILE_TEAMLAB + 0x0001; exports.AVS_OFFICESTUDIO_FILE_TEAMLAB_XLSY = exports.AVS_OFFICESTUDIO_FILE_TEAMLAB + 0x0002; exports.AVS_OFFICESTUDIO_FILE_TEAMLAB_PPTY = exports.AVS_OFFICESTUDIO_FILE_TEAMLAB + 0x0003; + exports.AVS_OFFICESTUDIO_FILE_CANVAS = 0x2000; exports.AVS_OFFICESTUDIO_FILE_CANVAS_WORD = exports.AVS_OFFICESTUDIO_FILE_CANVAS + 0x0001; exports.AVS_OFFICESTUDIO_FILE_CANVAS_SPREADSHEET = exports.AVS_OFFICESTUDIO_FILE_CANVAS + 0x0002; exports.AVS_OFFICESTUDIO_FILE_CANVAS_PRESENTATION = exports.AVS_OFFICESTUDIO_FILE_CANVAS + 0x0003; +exports.AVS_OFFICESTUDIO_FILE_CANVAS_PDF = exports.AVS_OFFICESTUDIO_FILE_CANVAS + 0x0004; + +exports.AVS_OFFICESTUDIO_FILE_DRAW = 0x4000; +exports.AVS_OFFICESTUDIO_FILE_DRAW_VSDX = exports.AVS_OFFICESTUDIO_FILE_DRAW + 0x0001; +exports.AVS_OFFICESTUDIO_FILE_DRAW_VSSX = exports.AVS_OFFICESTUDIO_FILE_DRAW + 0x0002; +exports.AVS_OFFICESTUDIO_FILE_DRAW_VSTX = exports.AVS_OFFICESTUDIO_FILE_DRAW + 0x0003; +exports.AVS_OFFICESTUDIO_FILE_DRAW_VSDM = exports.AVS_OFFICESTUDIO_FILE_DRAW + 0x0004; +exports.AVS_OFFICESTUDIO_FILE_DRAW_VSSM = exports.AVS_OFFICESTUDIO_FILE_DRAW + 0x0005; +exports.AVS_OFFICESTUDIO_FILE_DRAW_VSTM = exports.AVS_OFFICESTUDIO_FILE_DRAW + 0x0006; exports.NO_ERROR = 0; exports.UNKNOWN = -1; exports.READ_REQUEST_STREAM = -3; exports.WEB_REQUEST = -4; +exports.CHANGE_DOC_INFO = -5; exports.TASK_QUEUE = -20; exports.TASK_RESULT = -40; exports.STORAGE = -60; @@ -139,12 +222,19 @@ exports.CONVERT_DOWNLOAD = -81; exports.CONVERT_UNKNOWN_FORMAT = -82; exports.CONVERT_TIMEOUT = -83; exports.CONVERT_READ_FILE = -84; +exports.CONVERT_DRM_UNSUPPORTED = -85; exports.CONVERT_CORRUPTED = -86; exports.CONVERT_LIBREOFFICE = -87; exports.CONVERT_PARAMS = -88; exports.CONVERT_NEED_PARAMS = -89; exports.CONVERT_DRM = -90; exports.CONVERT_PASSWORD = -91; +exports.CONVERT_ICU = -92; +exports.CONVERT_LIMITS = -93; +exports.CONVERT_TEMPORARY = -94; +exports.CONVERT_DETECT = -95; +exports.CONVERT_CELLLIMITS = -96; +exports.CONVERT_DEAD_LETTER = -99; exports.UPLOAD = -100; exports.UPLOAD_CONTENT_LENGTH = -101; exports.UPLOAD_EXTENSION = -102; @@ -157,12 +247,16 @@ exports.VKEY_USER_COUNT_EXCEED = -123; exports.VKEY_TIME_EXPIRE = -124; exports.VKEY_TIME_INCORRECT = -125; exports.EDITOR_CHANGES = -160; +exports.PASSWORD = -180; -exports.QUEUE_PRIORITY_VERY_LOW = 0; -exports.QUEUE_PRIORITY_LOW = 1; -exports.QUEUE_PRIORITY_NORMAL = 2; -exports.QUEUE_PRIORITY_HIGH = 3; -exports.QUEUE_PRIORITY_VERY_HIGH = 4; +//Quorum queues internally only support two priorities: high and normal. +//Messages without a priority set will be mapped to normal as will priorities 0 - 4. +//Messages with a priority higher than 4 will be mapped to high. +exports.QUEUE_PRIORITY_VERY_LOW = 2; +exports.QUEUE_PRIORITY_LOW = 3; +exports.QUEUE_PRIORITY_NORMAL = 4; +exports.QUEUE_PRIORITY_HIGH = 5; +exports.QUEUE_PRIORITY_VERY_HIGH = 6; exports.EDITOR_TYPE_WORD = 0; exports.EDITOR_TYPE_SPREADSHEET = 1; @@ -171,23 +265,9 @@ exports.EDITOR_TYPE_CONVERTATION = 3; exports.PACKAGE_TYPE_OS = 0; exports.PACKAGE_TYPE_I = 1; +exports.PACKAGE_TYPE_D = 2; -exports.REDIS_KEY_PUBSUB = 'pubsub'; -exports.REDIS_KEY_SAVE_LOCK = 'savelock:'; -exports.REDIS_KEY_PRESENCE_HASH = 'presence:hash:'; -exports.REDIS_KEY_PRESENCE_SET = 'presence:set:'; -exports.REDIS_KEY_LOCKS = 'locks:'; -exports.REDIS_KEY_CHANGES_INDEX = 'changesindex:'; -exports.REDIS_KEY_LOCK_DOCUMENT = 'lockdocument:'; -exports.REDIS_KEY_MESSAGE = 'message:'; -exports.REDIS_KEY_DOCUMENTS = 'documents'; -exports.REDIS_KEY_LAST_SAVE = 'lastsave:'; -exports.REDIS_KEY_FORCE_SAVE = 'forcesave:'; -exports.REDIS_KEY_FORCE_SAVE_TIMER = 'forcesavetimer'; -exports.REDIS_KEY_FORCE_SAVE_TIMER_LOCK = 'forcesavetimerlock:'; -exports.REDIS_KEY_SAVED = 'saved:'; exports.REDIS_KEY_SHUTDOWN = 'shutdown'; -exports.REDIS_KEY_COLLECT_LOST = 'collectlost'; exports.REDIS_KEY_LICENSE = 'license'; exports.REDIS_KEY_LICENSE_T = 'licenseT'; @@ -203,8 +283,55 @@ exports.JWT_EXPIRED_CODE = 4005; exports.JWT_EXPIRED_REASON = 'token:'; exports.JWT_ERROR_CODE = 4006; exports.JWT_ERROR_REASON = 'token:'; +exports.DROP_CODE = 4007; +exports.DROP_REASON = 'drop'; +exports.UPDATE_VERSION_CODE = 4008; +exports.UPDATE_VERSION = 'update version'; +exports.NO_CACHE_CODE = 4009; +exports.NO_CACHE = 'no cache'; +exports.RESTORE_CODE = 4010; +exports.RESTORE = 'no cache'; exports.CONTENT_DISPOSITION_INLINE = 'inline'; exports.CONTENT_DISPOSITION_ATTACHMENT = 'attachment'; -exports.CONN_CLOSED = 3; +exports.CONN_CLOSED = "closed"; + +exports.FILE_STATUS_OK = 'ok'; +exports.FILE_STATUS_UPDATE_VERSION = 'updateversion'; + +exports.ACTIVEMQ_QUEUE_PREFIX = 'queue://'; +exports.ACTIVEMQ_TOPIC_PREFIX = 'topic://'; + +exports.TEMPLATES_DEFAULT_LOCALE = 'en-US'; +exports.TEMPLATES_FOLDER_LOCALE_COLLISON_MAP = { + 'en': 'en-US', + 'pt': 'pt-BR', + 'zh': 'zh-CH', + 'pt-PT': 'pt-PT', + 'zh-TW': 'zh-TW' +}; +exports.TABLE_RESULT_SCHEMA = [ + 'tenant', + 'id', + 'status', + 'status_info', + 'created_at', + 'last_open_date', + 'user_index', + 'change_id', + 'callback', + 'baseurl', + 'password', + 'additional' +]; +exports.TABLE_CHANGES_SCHEMA = [ + 'tenant', + 'id', + 'change_id', + 'user_id', + 'user_id_original', + 'user_name', + 'change_data', + 'change_date', +]; diff --git a/Common/sources/formatchecker.js b/Common/sources/formatchecker.js index ed6cda2cc4..654391b2bc 100644 --- a/Common/sources/formatchecker.js +++ b/Common/sources/formatchecker.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -34,11 +34,12 @@ var path = require('path'); var constants = require('./constants'); -var logger = require('./logger'); +const {open} = require("node:fs/promises"); function getImageFormatBySignature(buffer) { var length = buffer.length; - var startText = buffer.toString('ascii', 0, 20); + //1000 for svg(xml header and creator comment) + var startText = buffer.toString('ascii', 0, 1000); //jpeg // Hex: FF D8 FF @@ -188,18 +189,22 @@ function getImageFormatBySignature(buffer) { } //svg - //работает Ð´Ð»Ñ svg Ñделаных в редакторе, внешние svg могуть быть Ñ Ð¿Ñ€Ð¾Ð±ÐµÐ»Ð°Ð¼Ð¸ в начале - if (0 == startText.indexOf(' 0 && '.' == optExt[0]) { - optExt = optExt.substring(1); - } - format = exports.getFormatFromString(optExt); - } - } } catch (e) { - logger.error(optExt); - logger.error('error getImageFormat:\r\n%s', e.stack); + ctx.logger.error('error getImageFormat: %s', e.stack); } return format; }; +exports.isDocumentFormat = function(format) { + return 0 !== (format & constants.AVS_OFFICESTUDIO_FILE_DOCUMENT) || + format === constants.AVS_OFFICESTUDIO_FILE_CANVAS_WORD || + format === constants.AVS_OFFICESTUDIO_FILE_TEAMLAB_DOCY; +}; +exports.isSpreadsheetFormat = function(format) { + return 0 !== (format & constants.AVS_OFFICESTUDIO_FILE_SPREADSHEET) || + format === constants.AVS_OFFICESTUDIO_FILE_CANVAS_SPREADSHEET || + format === constants.AVS_OFFICESTUDIO_FILE_TEAMLAB_XLSY; +}; +exports.isPresentationFormat = function(format) { + return 0 !== (format & constants.AVS_OFFICESTUDIO_FILE_PRESENTATION) || + format === constants.AVS_OFFICESTUDIO_FILE_CANVAS_PRESENTATION || + format === constants.AVS_OFFICESTUDIO_FILE_TEAMLAB_PPTY; +}; +exports.isOOXFormat = function(format) { + return constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCX === format + || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCM === format + || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOTX === format + || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOTM === format + || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM === format + || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF === format + || constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF === format + || constants.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTX === format + || constants.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPSX === format + || constants.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPTM === format + || constants.AVS_OFFICESTUDIO_FILE_PRESENTATION_PPSM === format + || constants.AVS_OFFICESTUDIO_FILE_PRESENTATION_POTX === format + || constants.AVS_OFFICESTUDIO_FILE_PRESENTATION_POTM === format + || constants.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSX === format + || constants.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLSM === format + || constants.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLTX === format + || constants.AVS_OFFICESTUDIO_FILE_SPREADSHEET_XLTM === format; +}; +exports.isBrowserEditorFormat = function(format) { + return constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === format || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === format || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_DJVU === format || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_XPS === format; +}; +function getDocumentFormatBySignature(buffer) { + if (!buffer) { + return constants.AVS_OFFICESTUDIO_FILE_UNKNOWN; + } + let text = buffer.toString("latin1"); + // Check for binary DOCT format. + if (4 <= text.length && text[0] === 'D' && text[1] === 'O' && text[2] === 'C' && text[3] === 'Y') { + return constants.AVS_OFFICESTUDIO_FILE_CANVAS_WORD; + } + + // Check for binary XLST format + if (4 <= text.length && text[0] === 'X' && text[1] === 'L' && text[2] === 'S' && text[3] === 'Y') { + return constants.AVS_OFFICESTUDIO_FILE_CANVAS_SPREADSHEET; + } + + // Check for binary PPTT format + if (4 <= text.length && text[0] === 'P' && text[1] === 'P' && text[2] === 'T' && text[3] === 'Y') { + return constants.AVS_OFFICESTUDIO_FILE_CANVAS_PRESENTATION; + } + + // Unknown format + return constants.AVS_OFFICESTUDIO_FILE_UNKNOWN; +}; +async function getDocumentFormatByFile(file) { + let firstBytesLen = 100; + let buffer; + let fd; + try { + fd = await open(file, 'r'); + const stream = fd.createReadStream({ start: 0, end: firstBytesLen }); + let chunks = []; + for await (const chunk of stream) { + chunks.push(Buffer.from(chunk)); + } + buffer = Buffer.concat(chunks); + } finally { + await fd?.close(); + } + return getDocumentFormatBySignature(buffer); +}; +exports.getDocumentFormatBySignature = getDocumentFormatBySignature; +exports.getDocumentFormatByFile = getDocumentFormatByFile diff --git a/Common/sources/license.js b/Common/sources/license.js index 85fbe16480..d43ee21051 100644 --- a/Common/sources/license.js +++ b/Common/sources/license.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,104 +32,36 @@ 'use strict'; -const crypto = require('crypto'); -const fs = require('fs'); -const config = require('config'); -const configL = config.get('license'); const constants = require('./constants'); -const logger = require('./logger'); -const utils = require('./utils'); -const pubsubRedis = require('./../../DocService/sources/pubsubRedis'); -const redisClient = pubsubRedis.getClientRedis(); const buildDate = '6/29/2016'; const oBuildDate = new Date(buildDate); -const oPackageType = constants.PACKAGE_TYPE_OS; -const cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix'); -const redisKeyLicense = cfgRedisPrefix + ((constants.PACKAGE_TYPE_OS === oPackageType) ? constants.REDIS_KEY_LICENSE : - constants.REDIS_KEY_LICENSE_T); - -exports.readLicense = function*() { +exports.readLicense = async function () { const c_LR = constants.LICENSE_RESULT; - const resMax = {count: 999999, type: c_LR.Success}; - const res = {count: 1, type: c_LR.Error, light: false, packageType: oPackageType, trial: false, branding: false}; - let checkFile = false; - try { - const oFile = fs.readFileSync(configL.get('license_file')).toString(); - checkFile = true; - const oLicense = JSON.parse(oFile); - const sign = oLicense['signature']; - delete oLicense['signature']; - - const verify = crypto.createVerify('RSA-SHA1'); - verify.update(JSON.stringify(oLicense)); - const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRhGF7X4A0ZVlEg594WmODVVUI\niiPQs04aLmvfg8SborHss5gQXu0aIdUT6nb5rTh5hD2yfpF2WIW6M8z0WxRhwicg\nXwi80H1aLPf6lEPPLvN29EhQNjBpkFkAJUbS8uuhJEeKw0cE49g80eBBF4BCqSL6\nPFQbP9/rByxdxEoAIQIDAQAB\n-----END PUBLIC KEY-----\n'; - if (verify.verify(publicKey, sign, 'hex')) { - const endDate = new Date(oLicense['end_date']); - const isTrial = res.trial = (true === oLicense['trial'] || 'true' === oLicense['trial']); // Someone who likes to put json string instead of bool - const checkDate = (isTrial && constants.PACKAGE_TYPE_OS === oPackageType) ? new Date() : oBuildDate; - if (endDate >= checkDate && 2 <= oLicense['version']) { - res.count = Math.min(Math.max(res.count, oLicense['process'] >> 0), resMax.count); - res.type = c_LR.Success; - } else { - res.type = isTrial ? c_LR.ExpiredTrial : c_LR.Expired; - } - - res.light = (true === oLicense['light'] || 'true' === oLicense['light']); // Someone who likes to put json string instead of bool - res.branding = (true === oLicense['branding'] || 'true' === oLicense['branding']); // Someone who likes to put json string instead of bool - } else { - throw 'verify'; - } - } catch (e) { - res.count = 1; - res.type = c_LR.Error; - - if (checkFile) { - res.type = c_LR.ExpiredTrial; - } else { - if (constants.PACKAGE_TYPE_OS === oPackageType) { - if (yield* _getFileState()) { - res.type = c_LR.ExpiredTrial; - } - } else { - res.type = (yield* _getFileState()) ? c_LR.Success : c_LR.ExpiredTrial; - if (res.type === c_LR.Success) { - res.trial = true; - res.count = 2; - return res; - } - } - } - } - if (res.type === c_LR.Expired || res.type === c_LR.ExpiredTrial) { - res.count = 1; - logger.error('License Expired!!!'); - } - - if (checkFile) { - yield* _updateFileState(true); - } - - return res; + var now = new Date(); + var startDate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));//first day of current month + return [{ + count: 1, + type: c_LR.Success, + packageType: constants.PACKAGE_TYPE_OS, + mode: constants.LICENSE_MODE.None, + branding: false, + connections: constants.LICENSE_CONNECTIONS, + connectionsView: constants.LICENSE_CONNECTIONS, + customization: false, + advancedApi: false, + usersCount: 0, + usersViewCount: 0, + usersExpire: constants.LICENSE_EXPIRE_USERS_ONE_DAY, + hasLicense: false, + buildDate: oBuildDate, + startDate: startDate, + endDate: null, + customerId: "", + alias: "", + multitenancy: false + }, null]; }; -function* _getFileState() { - const val = yield utils.promiseRedis(redisClient, redisClient.hget, redisKeyLicense, redisKeyLicense); - if (constants.PACKAGE_TYPE_OS === oPackageType) { - return val; - } - - if (null === val) { - yield* _updateFileState(false); - return true; - } - - let now = new Date(); - now.setMonth(now.getMonth() - 1); - return (0 >= (now - new Date(val))); -} -function* _updateFileState(state) { - const val = constants.PACKAGE_TYPE_OS === oPackageType ? redisKeyLicense : (state ? new Date(1) : new Date()); - yield utils.promiseRedis(redisClient, redisClient.hset, redisKeyLicense, redisKeyLicense, val); -} +exports.packageType = constants.PACKAGE_TYPE_OS; diff --git a/Common/sources/logger.js b/Common/sources/logger.js index 21e46147ea..5e71c95bd3 100644 --- a/Common/sources/logger.js +++ b/Common/sources/logger.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -33,12 +33,49 @@ 'use strict'; var config = require('config'); +var util = require('util'); var log4js = require('log4js'); -log4js.configure(config.get('log.filePath'), config.get('log.options')); + +// https://stackoverflow.com/a/36643588 +var dateToJSONWithTZ = function (d) { + var timezoneOffsetInHours = -(d.getTimezoneOffset() / 60); //UTC minus local time + var sign = timezoneOffsetInHours >= 0 ? '+' : '-'; + var leadingZero = (Math.abs(timezoneOffsetInHours) < 10) ? '0' : ''; + + //It's a bit unfortunate that we need to construct a new Date instance + //(we don't want _d_ Date instance to be modified) + var correctedDate = new Date(d.getFullYear(), d.getMonth(), + d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds(), + d.getMilliseconds()); + correctedDate.setHours(d.getHours() + timezoneOffsetInHours); + var iso = correctedDate.toISOString().replace('Z', ''); + return iso + sign + leadingZero + Math.abs(timezoneOffsetInHours).toString() + ':00'; +}; + +log4js.addLayout('json', function(config) { + return function(logEvent) { + logEvent['startTime'] = dateToJSONWithTZ(logEvent['startTime']); + logEvent['message'] = util.format(...logEvent['data']); + delete logEvent['data']; + return JSON.stringify(logEvent); + } +}); + +log4js.configure(config.get('log.filePath')); var logger = log4js.getLogger('nodeJS'); +if (config.get('log.options.replaceConsole')) { + console.log = logger.info.bind(logger); + console.info = logger.info.bind(logger); + console.warn = logger.warn.bind(logger); + console.error = logger.error.bind(logger); + console.debug = logger.debug.bind(logger); +} +exports.getLogger = function (){ + return log4js.getLogger.apply(log4js, Array.prototype.slice.call(arguments)); +}; exports.trace = function (){ return logger.trace.apply(logger, Array.prototype.slice.call(arguments)); }; @@ -59,4 +96,4 @@ exports.fatal = function (){ }; exports.shutdown = function (callback) { return log4js.shutdown(callback); -}; \ No newline at end of file +}; diff --git a/Common/sources/mailService.js b/Common/sources/mailService.js new file mode 100644 index 0000000000..6db4c41e0f --- /dev/null +++ b/Common/sources/mailService.js @@ -0,0 +1,102 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2023 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const config = require('config'); +const nodemailer = require('nodemailer'); + +const cfgConnection = config.get('email.connectionConfiguration'); + +const connectionDefaultSettings = { + pool: true, + socketTimeout: 1000 * 60 * 2, + connectionTimeout: 1000 * 60 * 2, + maxConnections: 10 +}; +// Connection settings could be overridden by config, so user can configure transporter anyhow. +const settings = Object.assign(connectionDefaultSettings, cfgConnection); +const smtpTransporters = new Map(); + +function createTransporter(ctx, host, port, auth, messageCommonParameters = {}) { + const server = { + host, + port, + auth, + secure: port === 465 + }; + const transport = Object.assign({}, server, settings); + + try { + if (smtpTransporters.has(`${host}:${auth.user}`)) { + return; + } + + const transporter = nodemailer.createTransport(transport, messageCommonParameters); + smtpTransporters.set(`${host}:${auth.user}`, transporter); + } catch (error) { + ctx.logger.error('Mail service smtp transporter creation error: %o\nWith parameters: \n\thost - %s, \n\tport - %d, \n\tauth = %o', error.stack, host, port, auth); + } +} + +async function send(host, userLogin, mailObject) { + const transporter = smtpTransporters.get(`${host}:${userLogin}`); + if (!transporter) { + throw new Error(`MailService: no transporter exists for host "${host}" and user "${userLogin}"`); + } + + return transporter.sendMail(mailObject); +} + +function deleteTransporter(ctx, host, userLogin) { + const transporter = smtpTransporters.get(`${host}:${userLogin}`); + if (!transporter) { + ctx.logger.error(`MailService: no transporter exists for host "${host}" and user "${userLogin}"`); + return; + } + + transporter.close(); + smtpTransporters.delete(`${host}:${userLogin}`); +} + +function transportersRelease() { + smtpTransporters.forEach(transporter => transporter.close()); + smtpTransporters.clear(); +} + +module.exports = { + createTransporter, + send, + deleteTransporter, + transportersRelease +}; + diff --git a/Common/sources/notificationService.js b/Common/sources/notificationService.js new file mode 100644 index 0000000000..afb146a541 --- /dev/null +++ b/Common/sources/notificationService.js @@ -0,0 +1,150 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2023 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; +const util = require('util'); +const config = require('config'); +const ms = require('ms'); + +const mailService = require('./mailService'); + +const cfgMailServer = config.get('email.smtpServerConfiguration'); +const cfgMailMessageDefaults = config.get('email.contactDefaults'); +const cfgEditorDataStorage = config.get('services.CoAuthoring.server.editorDataStorage'); +const cfgEditorStatStorage = config.get('services.CoAuthoring.server.editorStatStorage'); +const editorStatStorage = require('./../../DocService/sources/' + (cfgEditorStatStorage || cfgEditorDataStorage)); + +const editorStat = editorStatStorage.EditorStat ? new editorStatStorage.EditorStat() : new editorStatStorage(); +const notificationTypes = { + LICENSE_EXPIRATION_WARNING: 'licenseExpirationWarning', + LICENSE_EXPIRATION_ERROR: 'licenseExpirationError', + LICENSE_LIMIT_EDIT: 'licenseLimitEdit', + LICENSE_LIMIT_LIVE_VIEWER: 'licenseLimitLiveViewer' +}; + +class TransportInterface { + async send(ctx, message) {} + contentGeneration(title, message) {} +} + +class MailTransport extends TransportInterface { + host = cfgMailServer.host; + port = cfgMailServer.port; + auth = cfgMailServer.auth; + + constructor(ctx) { + super(); + + mailService.createTransporter(ctx, this.host, this.port, this.auth, cfgMailMessageDefaults); + } + + async send(ctx, message) { + ctx.logger.debug('Notification service: MailTransport send %j', message); + return mailService.send(this.host, this.auth.user, message); + } + + contentGeneration(title, message) { + return { + subject: title, + text: message + }; + } +} + +// TODO: +class TelegramTransport extends TransportInterface { + constructor(ctx) { + super(); + } +} + +class Transport { + transport = new TransportInterface(); + + constructor(ctx, transportName) { + this.name = transportName; + + switch (transportName) { + case 'email': + this.transport = new MailTransport(ctx); + break; + case 'telegram': + this.transport = new TelegramTransport(ctx); + break + default: + ctx.logger.warn(`Notification service: error: transport method "${transportName}" not implemented`); + } + } +} + +async function notify(ctx, notificationType, title, message, opt_cacheKey = undefined) { + const tenRule = ctx.getCfg(`notification.rules.${notificationType}`, config.get(`notification.rules.${notificationType}`)); + if (tenRule?.enable) { + ctx.logger.debug('Notification service: notify "%s"', notificationType); + let checkRes = await checkRulePolicies(ctx, notificationType, tenRule, opt_cacheKey); + if (checkRes) { + await notifyRule(ctx, tenRule, title, message); + } + } +} + +async function checkRulePolicies(ctx, notificationType, tenRule, opt_cacheKey) { + const { repeatInterval } = tenRule.policies; + //decrease repeatInterval by 1% to avoid race condition if timeout=repeatInterval + let ttl = Math.floor(ms(repeatInterval) * 0.99 / 1000); + let isLock = false; + //todo for compatibility remove if after 8.2 + if (editorStat?.lockNotification) { + isLock = await editorStat.lockNotification(ctx, opt_cacheKey || notificationType, ttl); + } + if (!isLock) { + ctx.logger.debug(`Notification service: skip rule "%s" due to repeat interval = %s`, notificationType, repeatInterval); + } + return isLock; +} + +async function notifyRule(ctx, tenRule, title, message) { + const transportObjects = tenRule.transportType.map(transport => new Transport(ctx, transport)); + for (const transportObject of transportObjects) { + try { + const mail = transportObject.transport.contentGeneration(title, message); + await transportObject.transport.send(ctx, mail); + } catch (error) { + ctx.logger.error('Notification service: error: %s', error.stack); + } + } +} + +module.exports = { + notificationTypes, + notify +}; diff --git a/Common/sources/operationContext.js b/Common/sources/operationContext.js new file mode 100644 index 0000000000..78fe402fb5 --- /dev/null +++ b/Common/sources/operationContext.js @@ -0,0 +1,152 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const utils = require('./utils'); +const logger = require('./logger'); +const constants = require('./constants'); +const tenantManager = require('./tenantManager'); + +function Context(){ + this.logger = logger.getLogger('nodeJS'); + this.initDefault(); +} +Context.prototype.init = function(tenant, docId, userId, opt_shardKey, opt_WopiSrc) { + this.setTenant(tenant); + this.setDocId(docId); + this.setUserId(userId); + this.setShardKey(opt_shardKey); + this.setWopiSrc(opt_WopiSrc); + + this.config = null; + this.secret = null; + this.license = null; +}; +Context.prototype.initDefault = function() { + this.init(tenantManager.getDefautTenant(), constants.DEFAULT_DOC_ID, constants.DEFAULT_USER_ID, undefined); +}; +Context.prototype.initFromConnection = function(conn) { + let tenant = tenantManager.getTenantByConnection(this, conn); + let docId = conn.docid; + if (!docId) { + let handshake = conn.handshake; + const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(handshake.url); + if (docIdParsed && 1 < docIdParsed.length) { + docId = docIdParsed[1]; + } + } + let userId = conn.user?.id; + let shardKey = utils.getShardKeyByConnection(this, conn); + let wopiSrc = utils.getWopiSrcByConnection(this, conn); + this.init(tenant, docId || this.docId, userId || this.userId, shardKey, wopiSrc); +}; +Context.prototype.initFromRequest = function(req) { + let tenant = tenantManager.getTenantByRequest(this, req); + let shardKey = utils.getShardKeyByRequest(this, req); + let wopiSrc = utils.getWopiSrcByRequest(this, req); + this.init(tenant, this.docId, this.userId, shardKey, wopiSrc); +}; +Context.prototype.initFromTaskQueueData = function(task) { + let ctx = task.getCtx(); + this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc); +}; +Context.prototype.initFromPubSub = function(data) { + let ctx = data.ctx; + this.init(ctx.tenant, ctx.docId, ctx.userId, ctx.shardKey, ctx.wopiSrc); +}; +Context.prototype.initTenantCache = async function() { + this.config = await tenantManager.getTenantConfig(this); + //todo license and secret +}; + +Context.prototype.setTenant = function(tenant) { + this.tenant = tenant; + this.logger.addContext('TENANT', tenant); +}; +Context.prototype.setDocId = function(docId) { + this.docId = docId; + this.logger.addContext('DOCID', docId); +}; +Context.prototype.setUserId = function(userId) { + this.userId = userId; + this.logger.addContext('USERID', userId); +}; +Context.prototype.setShardKey = function(shardKey) { + this.shardKey = shardKey; +}; +Context.prototype.setWopiSrc = function(wopiSrc) { + this.wopiSrc = wopiSrc; +}; +Context.prototype.toJSON = function() { + return { + tenant: this.tenant, + docId: this.docId, + userId: this.userId, + shardKey: this.shardKey, + wopiSrc: this.wopiSrc + } +}; +Context.prototype.getCfg = function(property, defaultValue) { + if (this.config){ + return getImpl(this.config, property) ?? defaultValue; + } + return defaultValue; +}; + +/** + * Underlying get mechanism + * + * @private + * @method getImpl + * @param object {object} - Object to get the property for + * @param property {string | array[string]} - The property name to get (as an array or '.' delimited string) + * @return value {*} - Property value, including undefined if not defined. + */ +function getImpl(object, property) { + //from https://github.com/node-config/node-config/blob/a8b91ac86b499d11b90974a2c9915ce31266044a/lib/config.js#L137 + var t = this, + elems = Array.isArray(property) ? property : property.split('.'), + name = elems[0], + value = object[name]; + if (elems.length <= 1) { + return value; + } + // Note that typeof null === 'object' + if (value === null || typeof value !== 'object') { + return undefined; + } + return getImpl(value, elems.slice(1)); +}; + +exports.Context = Context; +exports.global = new Context(); diff --git a/Common/sources/rabbitMQCore.js b/Common/sources/rabbitMQCore.js index 6800c17809..c006d15dbb 100644 --- a/Common/sources/rabbitMQCore.js +++ b/Common/sources/rabbitMQCore.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -34,6 +34,7 @@ var config = require('config'); var amqp = require('amqplib/callback_api'); var logger = require('./logger'); +const operationContext = require('./operationContext'); var cfgRabbitUrl = config.get('rabbitmq.url'); var cfgRabbitSocketOptions = config.get('rabbitmq.socketOptions'); @@ -45,20 +46,20 @@ function connetPromise(closeCallback) { function startConnect() { amqp.connect(cfgRabbitUrl, cfgRabbitSocketOptions, function(err, conn) { if (null != err) { - logger.error('[AMQP] %s', err.stack); + operationContext.global.logger.error('[AMQP] %s', err.stack); setTimeout(startConnect, RECONNECT_TIMEOUT); } else { conn.on('error', function(err) { - logger.error('[AMQP] conn error', err.stack); + operationContext.global.logger.error('[AMQP] conn error', err.stack); }); var closeEventCallback = function() { //in some case receive multiple close events conn.removeListener('close', closeEventCallback); - logger.debug('[AMQP] conn close'); + operationContext.global.logger.debug('[AMQP] conn close'); closeCallback(); }; conn.on('close', closeEventCallback); - logger.debug('[AMQP] connected'); + operationContext.global.logger.debug('[AMQP] connected'); resolve(conn); } }); @@ -140,3 +141,4 @@ module.exports.assertExchangePromise = assertExchangePromise; module.exports.assertQueuePromise = assertQueuePromise; module.exports.consumePromise = consumePromise; module.exports.closePromise = closePromise; +module.exports.RECONNECT_TIMEOUT = RECONNECT_TIMEOUT; diff --git a/Common/sources/statsdclient.js b/Common/sources/statsdclient.js index 49a802ff43..f5bbb3bb1d 100644 --- a/Common/sources/statsdclient.js +++ b/Common/sources/statsdclient.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under diff --git a/Common/sources/storage-base.js b/Common/sources/storage-base.js index 64c5ef9abf..084d45e35a 100644 --- a/Common/sources/storage-base.js +++ b/Common/sources/storage-base.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -31,71 +31,185 @@ */ 'use strict'; +const os = require('os'); +const cluster = require('cluster'); var config = require('config'); var utils = require('./utils'); -var logger = require('./logger'); -var storage = require('./' + config.get('storage.name')); -function getStoragePath(strPath) { - return strPath.replace(/\\/g, '/'); +const cfgCacheStorage = config.get('storage'); +const cfgPersistentStorage = utils.deepMergeObjects({}, cfgCacheStorage, config.get('persistentStorage')); + +const cacheStorage = require('./' + cfgCacheStorage.name); +const persistentStorage = require('./' + cfgPersistentStorage.name); +const tenantManager = require('./tenantManager'); + +const HEALTH_CHECK_KEY_MAX = 10000; + +function getStoragePath(ctx, strPath, opt_specialDir) { + opt_specialDir = opt_specialDir || cfgCacheStorage.cacheFolderName; + return opt_specialDir + '/' + tenantManager.getTenantPathPrefix(ctx) + strPath.replace(/\\/g, '/'); } -exports.getObject = function(strPath) { - return storage.getObject(getStoragePath(strPath)); -}; -exports.putObject = function(strPath, buffer, contentLength) { - return storage.putObject(getStoragePath(strPath), buffer, contentLength); -}; -exports.listObjects = function(strPath) { - return storage.listObjects(getStoragePath(strPath)).catch(function(e) { - logger.error('storage.listObjects:\r\n%s', e.stack); - return []; - }); -}; -exports.deleteObject = function(strPath) { - return storage.deleteObject(getStoragePath(strPath)); -}; -exports.deleteObjects = function(strPaths) { - var StoragePaths = strPaths.map(function(curValue) { - return getStoragePath(curValue); - }); - return storage.deleteObjects(StoragePaths); -}; -exports.deletePath = function(strPath) { - return exports.listObjects(getStoragePath(strPath)).then(function(list) { - return exports.deleteObjects(list); - }); -}; -exports.getSignedUrl = function(baseUrl, strPath, optUrlExpires, optFilename, opt_type) { - return storage.getSignedUrl(baseUrl, getStoragePath(strPath), optUrlExpires, optFilename, opt_type); -}; -exports.getSignedUrls = function(baseUrl, strPath, optUrlExpires) { - return exports.listObjects(getStoragePath(strPath)).then(function(list) { - return Promise.all(list.map(function(curValue) { - return exports.getSignedUrl(baseUrl, curValue, optUrlExpires); - })).then(function(urls) { - var outputMap = {}; - for (var i = 0; i < list.length && i < urls.length; ++i) { - outputMap[exports.getRelativePath(strPath, list[i])] = urls[i]; - } - return outputMap; +function getStorage(opt_specialDir) { + return opt_specialDir ? persistentStorage : cacheStorage; +} +function getStorageCfg(ctx, opt_specialDir) { + return opt_specialDir ? cfgPersistentStorage : cfgCacheStorage; +} +function canCopyBetweenStorage(storageCfgSrc, storageCfgDst) { + return storageCfgSrc.name === storageCfgDst.name && storageCfgSrc.endpoint === storageCfgDst.endpoint; +} +function isDiffrentPersistentStorage() { + return !canCopyBetweenStorage(cacheStorage, cfgPersistentStorage); +} + +async function headObject(ctx, strPath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.headObject(storageCfg, getStoragePath(ctx, strPath, opt_specialDir)); +} +async function getObject(ctx, strPath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.getObject(storageCfg, getStoragePath(ctx, strPath, opt_specialDir)); +} +async function createReadStream(ctx, strPath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.createReadStream(storageCfg, getStoragePath(ctx, strPath, opt_specialDir)); +} +async function putObject(ctx, strPath, buffer, contentLength, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.putObject(storageCfg, getStoragePath(ctx, strPath, opt_specialDir), buffer, contentLength); +} +async function uploadObject(ctx, strPath, filePath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.uploadObject(storageCfg, getStoragePath(ctx, strPath, opt_specialDir), filePath); +} +async function copyObject(ctx, sourceKey, destinationKey, opt_specialDirSrc, opt_specialDirDst) { + let storageSrc = getStorage(opt_specialDirSrc); + let storagePathSrc = getStoragePath(ctx, sourceKey, opt_specialDirSrc); + let storagePathDst = getStoragePath(ctx, destinationKey, opt_specialDirDst); + let storageCfgSrc = getStorageCfg(ctx, opt_specialDirSrc); + let storageCfgDst = getStorageCfg(ctx, opt_specialDirDst); + if (canCopyBetweenStorage(storageCfgSrc, storageCfgDst)){ + return await storageSrc.copyObject(storageCfgSrc, storageCfgDst, storagePathSrc, storagePathDst); + } else { + let storageDst = getStorage(opt_specialDirDst); + //todo stream + let buffer = await storageSrc.getObject(storageCfgSrc, storagePathSrc); + return await storageDst.putObject(storageCfgDst, storagePathDst, buffer, buffer.length); + } +} +async function copyPath(ctx, sourcePath, destinationPath, opt_specialDirSrc, opt_specialDirDst) { + let list = await listObjects(ctx, sourcePath, opt_specialDirSrc); + await Promise.all(list.map(function(curValue) { + return copyObject(ctx, curValue, destinationPath + '/' + getRelativePath(sourcePath, curValue), opt_specialDirSrc, opt_specialDirDst); + })); +} +async function listObjects(ctx, strPath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + let prefix = getStoragePath(ctx, "", opt_specialDir); + try { + let list = await storage.listObjects(storageCfg, getStoragePath(ctx, strPath, opt_specialDir)); + return list.map((currentValue) => { + return currentValue.substring(prefix.length); }); - }); -}; -exports.getSignedUrlsByArray = function(baseUrl, list, optPath, optUrlExpires) { - return Promise.all(list.map(function(curValue) { - return exports.getSignedUrl(baseUrl, curValue, optUrlExpires); - })).then(function(urls) { - var outputMap = {}; - for (var i = 0; i < list.length && i < urls.length; ++i) { - if (optPath) { - outputMap[exports.getRelativePath(optPath, list[i])] = urls[i]; - } else { - outputMap[list[i]] = urls[i]; - } + } catch (e) { + ctx.logger.error('storage.listObjects: %s', e.stack); + return []; + } +} +async function deleteObject(ctx, strPath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.deleteObject(storageCfg, getStoragePath(ctx, strPath, opt_specialDir)); +} +async function deletePath(ctx, strPath, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.deletePath(storageCfg, getStoragePath(ctx, strPath, opt_specialDir)); +} +async function getSignedUrl(ctx, baseUrl, strPath, urlType, optFilename, opt_creationDate, opt_specialDir) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + return await storage.getSignedUrl(ctx, storageCfg, baseUrl, getStoragePath(ctx, strPath, opt_specialDir), urlType, optFilename, opt_creationDate); +} +async function getSignedUrls(ctx, baseUrl, strPath, urlType, opt_creationDate, opt_specialDir) { + let storagePathSrc = getStoragePath(ctx, strPath, opt_specialDir); + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + let list = await storage.listObjects(storageCfg, storagePathSrc, storageCfg); + let urls = await Promise.all(list.map(function(curValue) { + return storage.getSignedUrl(ctx, storageCfg, baseUrl, curValue, urlType, undefined, opt_creationDate); + })); + let outputMap = {}; + for (let i = 0; i < list.length && i < urls.length; ++i) { + outputMap[getRelativePath(storagePathSrc, list[i])] = urls[i]; + } + return outputMap; +} +async function getSignedUrlsArrayByArray(ctx, baseUrl, list, urlType, opt_specialDir) { + return await Promise.all(list.map(function (curValue) { + let storage = getStorage(opt_specialDir); + let storageCfg = getStorageCfg(ctx, opt_specialDir); + let storagePathSrc = getStoragePath(ctx, curValue, opt_specialDir); + return storage.getSignedUrl(ctx, storageCfg, baseUrl, storagePathSrc, urlType, undefined); + })); +} +async function getSignedUrlsByArray(ctx, baseUrl, list, optPath, urlType, opt_specialDir) { + let urls = await getSignedUrlsArrayByArray(ctx, baseUrl, list, urlType, opt_specialDir); + var outputMap = {}; + for (var i = 0; i < list.length && i < urls.length; ++i) { + if (optPath) { + let storagePathSrc = getStoragePath(ctx, optPath, opt_specialDir); + outputMap[getRelativePath(storagePathSrc, list[i])] = urls[i]; + } else { + outputMap[list[i]] = urls[i]; } - return outputMap; - }); -}; -exports.getRelativePath = function(strBase, strPath) { + } + return outputMap; +} +function getRelativePath(strBase, strPath) { return strPath.substring(strBase.length + 1); +} +async function healthCheck(ctx, opt_specialDir) { + const clusterId = cluster.isWorker ? cluster.worker.id : ''; + const tempName = 'hc_' + os.hostname() + '_' + clusterId + '_' + Math.round(Math.random() * HEALTH_CHECK_KEY_MAX); + const tempBuffer = Buffer.from([1, 2, 3, 4, 5]); + try { + //It's proper to putObject one tempName + await putObject(ctx, tempName, tempBuffer, tempBuffer.length, opt_specialDir); + //try to prevent case, when another process can remove same tempName + await deleteObject(ctx, tempName, opt_specialDir); + } catch (err) { + ctx.logger.warn('healthCheck storage(%s) error %s', opt_specialDir, err.stack); + } +} +function needServeStatic(opt_specialDir) { + let storage = getStorage(opt_specialDir); + return storage.needServeStatic(); +} + +module.exports = { + headObject, + getObject, + createReadStream, + putObject, + uploadObject, + copyObject, + copyPath, + listObjects, + deleteObject, + deletePath, + getSignedUrl, + getSignedUrls, + getSignedUrlsArrayByArray, + getSignedUrlsByArray, + getRelativePath, + isDiffrentPersistentStorage, + healthCheck, + needServeStatic }; diff --git a/Common/sources/storage-fs.js b/Common/sources/storage-fs.js index 8403c3019d..4154cf6e08 100644 --- a/Common/sources/storage-fs.js +++ b/Common/sources/storage-fs.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,131 +32,152 @@ 'use strict'; -var fs = require('fs'); +const { cp, rm, mkdir } = require('fs/promises'); +const { stat, readFile, writeFile } = require('fs/promises'); var path = require('path'); -var mkdirp = require('mkdirp'); var utils = require("./utils"); var crypto = require('crypto'); +const ms = require('ms'); +const config = require('config'); +const commonDefines = require('./../../Common/sources/commondefines'); +const constants = require('./../../Common/sources/constants'); -var configStorage = require('config').get('storage'); -var cfgBucketName = configStorage.get('bucketName'); -var cfgStorageFolderName = configStorage.get('storageFolderName'); -var cfgStorageExternalHost = configStorage.get('externalHost'); -var configFs = configStorage.get('fs'); -var cfgStorageFolderPath = configFs.get('folderPath'); -var cfgStorageSecretString = configFs.get('secretString'); -var cfgStorageUrlExpires = configFs.get('urlExpires'); - -function getFilePath(strPath) { - return path.join(cfgStorageFolderPath, strPath); +const cfgExpSessionAbsolute = ms(config.get('services.CoAuthoring.expire.sessionabsolute')); + +//Stubs are needed until integrators pass these parameters to all requests +let shardKeyCached; +let wopiSrcCached; + +function getFilePath(storageCfg, strPath) { + const storageFolderPath = storageCfg.fs.folderPath; + return path.join(storageFolderPath, strPath); } function getOutputPath(strPath) { return strPath.replace(/\\/g, '/'); } -function removeEmptyParent(strPath, done) { - if (cfgStorageFolderPath.length + 1 >= strPath.length) { - done(); + +async function headObject(storageCfg, strPath) { + let fsPath = getFilePath(storageCfg, strPath); + let stats = await stat(fsPath); + return {ContentLength: stats.size}; +} + +async function getObject(storageCfg, strPath) { + let fsPath = getFilePath(storageCfg, strPath); + return await readFile(fsPath); +} + +async function createReadStream(storageCfg, strPath) { + let fsPath = getFilePath(storageCfg, strPath); + let stats = await stat(fsPath); + let contentLength = stats.size; + let readStream = await utils.promiseCreateReadStream(fsPath); + return { + contentLength: contentLength, + readStream: readStream + }; +} + +async function putObject(storageCfg, strPath, buffer, contentLength) { + var fsPath = getFilePath(storageCfg, strPath); + await mkdir(path.dirname(fsPath), {recursive: true}); + + if (Buffer.isBuffer(buffer)) { + await writeFile(fsPath, buffer); } else { - fs.readdir(strPath, function(err, list) { - if (err) { - //не реагируем на ошибку, потому Ñкорее вÑего Ñта папка удалилаÑÑŒ в ÑоÑеднем потоке - done(); - } else { - if (list.length > 0) { - done(); - } else { - fs.rmdir(strPath, function(err) { - if (err) { - //не реагируем на ошибку, потому Ñкорее вÑего Ñта папка удалилаÑÑŒ в ÑоÑеднем потоке - done(); - } else { - removeEmptyParent(path.dirname(strPath), function(err) { - done(err); - }); - } - }); - } - } - }); + let writable = await utils.promiseCreateWriteStream(fsPath); + await utils.pipeStreams(buffer, writable, true); } } -exports.getObject = function(strPath) { - return utils.readFile(getFilePath(strPath)); -}; +async function uploadObject(storageCfg, strPath, filePath) { + let fsPath = getFilePath(storageCfg, strPath); + await cp(filePath, fsPath, {force: true, recursive: true}); +} -exports.putObject = function(strPath, buffer, contentLength) { - return new Promise(function(resolve, reject) { - var fsPath = getFilePath(strPath); - mkdirp(path.dirname(fsPath), function(err) { - if (err) { - reject(err); - } else { - //todo 0666 - if (Buffer.isBuffer(buffer)) { - fs.writeFile(fsPath, buffer, function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - } else { - utils.promiseCreateWriteStream(fsPath).then(function(writable) { - buffer.pipe(writable); - }).catch(function(err) { - reject(err); - }); - } - } - }); - }); -}; -exports.listObjects = function(strPath) { - return utils.listObjects(getFilePath(strPath)).then(function(values) { - return values.map(function(curvalue) { - return getOutputPath(curvalue.substring(cfgStorageFolderPath.length + 1)); - }); - }); -}; -exports.deleteObject = function(strPath) { - return new Promise(function(resolve, reject) { - const fsPath = getFilePath(strPath); - fs.unlink(fsPath, function(err) { - if (err) { - reject(err); - } else { - //resolve(); - removeEmptyParent(path.dirname(fsPath), function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - } - }); - }); -}; -exports.deleteObjects = function(strPaths) { - return Promise.all(strPaths.map(exports.deleteObject)); -}; -exports.getSignedUrl = function(baseUrl, strPath, optUrlExpires, optFilename, opt_type) { - return new Promise(function(resolve, reject) { - //replace '/' with %2f before encodeURIComponent becase nginx determine %2f as '/' and get wrong system path - var userFriendlyName = optFilename ? encodeURIComponent(optFilename.replace(/\//g, "%2f")) : path.basename(strPath); - var uri = '/' + cfgBucketName + '/' + cfgStorageFolderName + '/' + strPath + '/' + userFriendlyName; - var url = (cfgStorageExternalHost ? cfgStorageExternalHost : baseUrl) + uri; - - var date = new Date(); - var expires = Math.ceil(date.getTime() / 1000) + (optUrlExpires || cfgStorageUrlExpires || 2592000); - - var md5 = crypto.createHash('md5').update(expires + decodeURIComponent(uri) + cfgStorageSecretString).digest("base64"); - md5 = md5.replace(/\+/g, "-"); - md5 = md5.replace(/\//g, "_"); - - url += ('?md5=' + md5 + '&expires=' + expires); - url += '&disposition=' + encodeURIComponent(utils.getContentDisposition(null, null, opt_type)); - resolve(utils.changeOnlyOfficeUrl(url, strPath, optFilename)); +async function copyObject(storageCfgSrc, storageCfgDst, sourceKey, destinationKey) { + let fsPathSource = getFilePath(storageCfgSrc, sourceKey); + let fsPathDestination = getFilePath(storageCfgDst, destinationKey); + await cp(fsPathSource, fsPathDestination, {force: true, recursive: true}); +} + +async function listObjects(storageCfg, strPath) { + const storageFolderPath = storageCfg.fs.folderPath; + let fsPath = getFilePath(storageCfg, strPath); + let values = await utils.listObjects(fsPath); + return values.map(function(curvalue) { + return getOutputPath(curvalue.substring(storageFolderPath.length + 1)); }); +} + +async function deleteObject(storageCfg, strPath) { + const fsPath = getFilePath(storageCfg, strPath); + return rm(fsPath, {force: true, recursive: true}); +} + +async function deletePath(storageCfg, strPath) { + const fsPath = getFilePath(storageCfg, strPath); + return rm(fsPath, {force: true, recursive: true, maxRetries: 3}); +} + +async function getSignedUrl(ctx, storageCfg, baseUrl, strPath, urlType, optFilename, opt_creationDate) { + const storageSecretString = storageCfg.fs.secretString; + const storageUrlExpires = storageCfg.fs.urlExpires; + const bucketName = storageCfg.bucketName; + const storageFolderName = storageCfg.storageFolderName; + //replace '/' with %2f before encodeURIComponent becase nginx determine %2f as '/' and get wrong system path + const userFriendlyName = optFilename ? encodeURIComponent(optFilename.replace(/\//g, "%2f")) : path.basename(strPath); + var uri = '/' + bucketName + '/' + storageFolderName + '/' + strPath + '/' + userFriendlyName; + //RFC 1123 does not allow underscores https://stackoverflow.com/questions/2180465/can-domain-name-subdomains-have-an-underscore-in-it + var url = utils.checkBaseUrl(ctx, baseUrl, storageCfg).replace(/_/g, "%5f"); + url += uri; + + var date = Date.now(); + let creationDate = opt_creationDate || date; + let expiredAfter = (commonDefines.c_oAscUrlTypes.Session === urlType ? (cfgExpSessionAbsolute / 1000) : storageUrlExpires) || 31536000; + //todo creationDate can be greater because mysql CURRENT_TIMESTAMP uses local time, not UTC + var expires = creationDate + Math.ceil(Math.abs(date - creationDate) / expiredAfter) * expiredAfter; + expires = Math.ceil(expires / 1000); + expires += expiredAfter; + + var md5 = crypto.createHash('md5').update(expires + decodeURIComponent(uri) + storageSecretString).digest("base64"); + md5 = md5.replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); + + url += '?md5=' + encodeURIComponent(md5); + url += '&expires=' + encodeURIComponent(expires); + if (ctx.shardKey) { + shardKeyCached = ctx.shardKey; + url += `&${constants.SHARD_KEY_API_NAME}=${encodeURIComponent(ctx.shardKey)}`; + } else if (ctx.wopiSrc) { + wopiSrcCached = ctx.wopiSrc; + url += `&${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(ctx.wopiSrc)}`; + } else if (process.env.DEFAULT_SHARD_KEY) { + //Set DEFAULT_SHARD_KEY from environment as shardkey in case of integrator did not pass this param + url += `&${constants.SHARD_KEY_API_NAME}=${encodeURIComponent(process.env.DEFAULT_SHARD_KEY)}`; + } else if (shardKeyCached) { + //Add stubs for shardkey params until integrators pass these parameters to all requests + url += `&${constants.SHARD_KEY_API_NAME}=${encodeURIComponent(shardKeyCached)}`; + } else if (wopiSrcCached) { + url += `&${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(wopiSrcCached)}`; + } + url += '&filename=' + userFriendlyName; + return url; +} + +function needServeStatic() { + return true; +} + +module.exports = { + headObject, + getObject, + createReadStream, + putObject, + uploadObject, + copyObject, + listObjects, + deleteObject, + deletePath, + getSignedUrl, + needServeStatic }; diff --git a/Common/sources/storage-s3.js b/Common/sources/storage-s3.js index dfc18d5a2b..bcba521928 100644 --- a/Common/sources/storage-s3.js +++ b/Common/sources/storage-s3.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -31,196 +31,240 @@ */ 'use strict'; -var url = require('url'); -var path = require('path'); -var AWS = require('aws-sdk'); -var mime = require('mime'); -var s3urlSigner = require('amazon-s3-url-signer'); -var utils = require('./utils'); +const fs = require('fs'); +const url = require('url'); +const { Agent } = require('https'); +const path = require('path'); +const { S3Client, ListObjectsCommand, HeadObjectCommand} = require("@aws-sdk/client-s3"); +const { GetObjectCommand, PutObjectCommand, CopyObjectCommand} = require("@aws-sdk/client-s3"); +const { DeleteObjectsCommand, DeleteObjectCommand } = require("@aws-sdk/client-s3"); +const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); +const { NodeHttpHandler } = require("@aws-sdk/node-http-handler"); +const mime = require('mime'); +const config = require('config'); +const utils = require('./utils'); +const ms = require('ms'); +const commonDefines = require('./../../Common/sources/commondefines'); + +const cfgExpSessionAbsolute = ms(config.get('services.CoAuthoring.expire.sessionabsolute')); +const cfgRequestDefaults = config.get('services.CoAuthoring.requestDefaults'); -var configStorage = require('config').get('storage'); -var cfgRegion = configStorage.get('region'); -var cfgEndpoint = configStorage.get('endpoint'); -var cfgBucketName = configStorage.get('bucketName'); -var cfgStorageFolderName = configStorage.get('storageFolderName'); -var cfgAccessKeyId = configStorage.get('accessKeyId'); -var cfgSecretAccessKey = configStorage.get('secretAccessKey'); -var cfgUseRequestToGetUrl = configStorage.get('useRequestToGetUrl'); -var cfgUseSignedUrl = configStorage.get('useSignedUrl'); -var cfgExternalHost = configStorage.get('externalHost'); -/** - * Don't hard-code your credentials! - * Export the following environment variables instead: - * - * export AWS_ACCESS_KEY_ID='AKID' - * export AWS_SECRET_ACCESS_KEY='SECRET' - */ -var configS3 = { - region: cfgRegion, - endpoint: cfgEndpoint, - accessKeyId: cfgAccessKeyId, - secretAccessKey: cfgSecretAccessKey -}; -if (configS3.endpoint) { - configS3.sslEnabled = false; - configS3.s3ForcePathStyle = true; -} -AWS.config.update(configS3); -var s3Client = new AWS.S3(); -if (configS3.endpoint) { - s3Client.endpoint = new AWS.Endpoint(configS3.endpoint); -} -var cfgEndpointParsed = null; -if (cfgEndpoint) { - cfgEndpointParsed = url.parse(cfgEndpoint); -} //This operation enables you to delete multiple objects from a bucket using a single HTTP request. You may specify up to 1000 keys. -var MAX_DELETE_OBJECTS = 1000; +const MAX_DELETE_OBJECTS = 1000; +let clients = {}; -function getFilePath(strPath) { - //todo - return cfgStorageFolderName + '/' + strPath; -} -function joinListObjects(inputArray, outputArray) { - var length = inputArray.length; - for (var i = 0; i < length; i++) { - outputArray.push(inputArray[i].Key.substring((cfgStorageFolderName + '/').length)); - } -} -function listObjectsExec(output, params, resolve, reject) { - s3Client.listObjects(params, function(err, data) { - if (err) { - reject(err); - } else { - joinListObjects(data.Contents, output); - if (data.IsTruncated && (data.NextMarker || data.Contents.length > 0)) { - params.Marker = data.NextMarker || data.Contents[data.Contents.length - 1].Key; - listObjectsExec(output, params, resolve, reject); - } else { - resolve(output); - } +function getS3Client(storageCfg) { + /** + * Don't hard-code your credentials! + * Export the following environment variables instead: + * + * export AWS_ACCESS_KEY_ID='AKID' + * export AWS_SECRET_ACCESS_KEY='SECRET' + */ + let configS3 = { + region: storageCfg.region, + endpoint: storageCfg.endpoint, + credentials : { + accessKeyId: storageCfg.accessKeyId, + secretAccessKey: storageCfg.secretAccessKey } + }; + + if (configS3.endpoint) { + configS3.tls = storageCfg.sslEnabled; + configS3.forcePathStyle = storageCfg.s3ForcePathStyle; + } + //todo dedicated options? + const agent = new Agent(cfgRequestDefaults); + configS3.requestHandler = new NodeHttpHandler({ + httpAgent: agent, + httpsAgent: agent }); + let configJson = JSON.stringify(configS3); + let client = clients[configJson]; + if (!client) { + client = new S3Client(configS3); + clients[configJson] = client; + } + return client; } -function mapDeleteObjects(currentValue) { - return {Key: currentValue}; + +function getFilePath(storageCfg, strPath) { + const storageFolderName = storageCfg.storageFolderName; + return storageFolderName + '/' + strPath; +} +function joinListObjects(storageCfg, inputArray, outputArray) { + if (!inputArray) { + return; + } + const storageFolderName = storageCfg.storageFolderName; + let length = inputArray.length; + for (let i = 0; i < length; i++) { + outputArray.push(inputArray[i].Key.substring((storageFolderName + '/').length)); + } } -function deleteObjectsHelp(aKeys) { - return new Promise(function(resolve, reject) { +async function listObjectsExec(storageCfg, output, params) { + const data = await getS3Client(storageCfg).send(new ListObjectsCommand(params)); + joinListObjects(storageCfg, data.Contents, output); + if (data.IsTruncated && (data.NextMarker || (data.Contents && data.Contents.length > 0))) { + params.Marker = data.NextMarker || data.Contents[data.Contents.length - 1].Key; + return await listObjectsExec(storageCfg, output, params); + } else { + return output; + } +} +async function deleteObjectsHelp(storageCfg, aKeys) { //By default, the operation uses verbose mode in which the response includes the result of deletion of each key in your request. //In quiet mode the response includes only keys where the delete operation encountered an error. - var params = {Bucket: cfgBucketName, Delete: {Objects: aKeys, Quiet: true}}; - s3Client.deleteObjects(params, function(err, data) { - if (err) { - reject(err); - } else { - resolve(data); + const input = { + Bucket: storageCfg.bucketName, + Delete: { + Objects: aKeys, + Quiet: true } - }); - }); + }; + const command = new DeleteObjectsCommand(input); + await getS3Client(storageCfg).send(command); } -exports.getObject = function(strPath) { - return new Promise(function(resolve, reject) { - var params = {Bucket: cfgBucketName, Key: getFilePath(strPath)}; - s3Client.getObject(params, function(err, data) { - if (err) { - reject(err); - } else { - resolve(data.Body); - } - }); - }); -}; -exports.putObject = function(strPath, buffer, contentLength) { - return new Promise(function(resolve, reject) { - //todo раÑÑмотреть Expires - var params = {Bucket: cfgBucketName, Key: getFilePath(strPath), Body: buffer, - ContentLength: contentLength, ContentType: mime.lookup(strPath)}; - s3Client.putObject(params, function(err, data) { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); -}; -exports.listObjects = function(strPath) { - return new Promise(function(resolve, reject) { - var params = {Bucket: cfgBucketName, Prefix: getFilePath(strPath)}; - var output = []; - listObjectsExec(output, params, resolve, reject); - }); -}; -exports.deleteObject = function(strPath) { - return new Promise(function(resolve, reject) { - var params = {Bucket: cfgBucketName, Key: getFilePath(strPath)}; - s3Client.deleteObject(params, function(err, data) { - if (err) { - reject(err); - } else { - resolve(data); - } - }); - }); -}; -exports.deleteObjects = function(strPaths) { - var aKeys = strPaths.map(function (currentValue) { - return {Key: getFilePath(currentValue)}; - }); - var deletePromises = []; - for (var i = 0; i < aKeys.length; i += MAX_DELETE_OBJECTS) { - deletePromises.push(deleteObjectsHelp(aKeys.slice(i, i + MAX_DELETE_OBJECTS))); +async function headObject(storageCfg, strPath) { + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath) + }; + const command = new HeadObjectCommand(input); + let output = await getS3Client(storageCfg).send(command); + return {ContentLength: output.ContentLength}; +} +async function getObject(storageCfg, strPath) { + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath) + }; + const command = new GetObjectCommand(input); + const output = await getS3Client(storageCfg).send(command); + + return await utils.stream2Buffer(output.Body); +} +async function createReadStream(storageCfg, strPath) { + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath) + }; + const command = new GetObjectCommand(input); + const output = await getS3Client(storageCfg).send(command); + return { + contentLength: output.ContentLength, + readStream: output.Body + }; +} +async function putObject(storageCfg, strPath, buffer, contentLength) { + //todo consider Expires + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath), + Body: buffer, + ContentLength: contentLength, + ContentType: mime.getType(strPath) + }; + const command = new PutObjectCommand(input); + await getS3Client(storageCfg).send(command); +} +async function uploadObject(storageCfg, strPath, filePath) { + const file = fs.createReadStream(filePath); + //todo раÑÑмотреть Expires + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath), + Body: file, + ContentType: mime.getType(strPath) + }; + const command = new PutObjectCommand(input); + await getS3Client(storageCfg).send(command); +} +async function copyObject(storageCfgSrc, storageCfgDst, sourceKey, destinationKey) { + //todo source bucket + const input = { + Bucket: storageCfgDst.bucketName, + Key: getFilePath(storageCfgDst, destinationKey), + CopySource: `/${storageCfgSrc.bucketName}/${getFilePath(storageCfgSrc, sourceKey)}` + }; + const command = new CopyObjectCommand(input); + await getS3Client(storageCfgDst).send(command); +} +async function listObjects(storageCfg, strPath) { + const prefix = getFilePath(storageCfg, strPath); + let params = { + Bucket: storageCfg.bucketName, + Prefix: prefix, + }; + let output = []; + await listObjectsExec(storageCfg, output, params); + if (output && Array.isArray(output) && output.length > 0) { + const escapedPath = strPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const regex = new RegExp(`^${escapedPath}$|^${escapedPath}\\/`); + output = output.filter(item => regex.test(item)); } - return Promise.all(deletePromises); + return output; +} +async function deleteObject(storageCfg, strPath) { + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath) + }; + const command = new DeleteObjectCommand(input); + await getS3Client(storageCfg).send(command); }; -exports.getSignedUrl = function(baseUrl, strPath, optUrlExpires, optFilename, opt_type) { - return new Promise(function(resolve, reject) { - var expires = optUrlExpires || 604800; - var userFriendlyName = optFilename ? optFilename.replace(/\//g, "%2f") : path.basename(strPath); - var contentDisposition = utils.getContentDispositionS3(userFriendlyName, null, opt_type); - if (cfgUseRequestToGetUrl) { - //default Expires 900 seconds - var params = { - Bucket: cfgBucketName, Key: getFilePath(strPath), ResponseContentDisposition: contentDisposition, Expires: expires - }; - s3Client.getSignedUrl('getObject', params, function(err, data) { - if (err) { - reject(err); - } else { - resolve(utils.changeOnlyOfficeUrl(data, strPath, optFilename)); - } - }); - } else { - var host; - if (cfgRegion) { - host = 'https://s3-'+cfgRegion+'.amazonaws.com'; - } else if (cfgEndpointParsed && - (cfgEndpointParsed.hostname == 'localhost' || cfgEndpointParsed.hostname == '127.0.0.1') && - 80 == cfgEndpointParsed.port) { - host = (cfgExternalHost ? cfgExternalHost : baseUrl) + cfgEndpointParsed.path; - } else { - host = cfgEndpoint; - } - if (host && host.length > 0 && '/' != host[host.length - 1]) { - host += '/'; - } - var newUrl; - if (cfgUseSignedUrl) { - //todo уйти от parse - var hostParsed = url.parse(host); - var protocol = hostParsed.protocol.substring(0, hostParsed.protocol.length - 1); - var signerOptions = { - host: hostParsed.hostname, port: hostParsed.port, - protocol: protocol, useSubdomain: false - }; - var awsUrlSigner = s3urlSigner.urlSigner(cfgAccessKeyId, cfgSecretAccessKey, signerOptions); - newUrl = awsUrlSigner.getUrl('GET', getFilePath(strPath), cfgBucketName, expires, contentDisposition); - } else { - newUrl = host + cfgBucketName + '/' + cfgStorageFolderName + '/' + strPath; - } - resolve(utils.changeOnlyOfficeUrl(newUrl, strPath, optFilename)); - } +async function deleteObjects(storageCfg, strPaths) { + let aKeys = strPaths.map(function (currentValue) { + return {Key: getFilePath(storageCfg, currentValue)}; }); + for (let i = 0; i < aKeys.length; i += MAX_DELETE_OBJECTS) { + await deleteObjectsHelp(storageCfg, aKeys.slice(i, i + MAX_DELETE_OBJECTS)); + } +} +async function deletePath(storageCfg, strPath) { + let list = await listObjects(storageCfg, strPath); + await deleteObjects(storageCfg, list); +} +async function getSignedUrlWrapper(ctx, storageCfg, baseUrl, strPath, urlType, optFilename, opt_creationDate) { + const storageUrlExpires = storageCfg.fs.urlExpires; + let expires = (commonDefines.c_oAscUrlTypes.Session === urlType ? cfgExpSessionAbsolute / 1000 : storageUrlExpires) || 31536000; + // Signature version 4 presigned URLs must have an expiration date less than one week in the future + expires = Math.min(expires, 604800); + let userFriendlyName = optFilename ? optFilename.replace(/\//g, "%2f") : path.basename(strPath); + let contentDisposition = utils.getContentDisposition(userFriendlyName, null, null); + + const input = { + Bucket: storageCfg.bucketName, + Key: getFilePath(storageCfg, strPath), + ResponseContentDisposition: contentDisposition + }; + const command = new GetObjectCommand(input); + //default Expires 900 seconds + let options = { + expiresIn: expires + }; + return await getSignedUrl(getS3Client(storageCfg), command, options); + //extra query params cause SignatureDoesNotMatch + //https://stackoverflow.com/questions/55503009/amazon-s3-signature-does-not-match-when-extra-query-params-ga-added-in-url + // return utils.changeOnlyOfficeUrl(url, strPath, optFilename); +} + +function needServeStatic() { + return false; +} + +module.exports = { + headObject, + getObject, + createReadStream, + putObject, + uploadObject, + copyObject, + listObjects, + deleteObject, + deletePath, + getSignedUrl: getSignedUrlWrapper, + needServeStatic }; diff --git a/Common/sources/taskqueueRabbitMQ.js b/Common/sources/taskqueueRabbitMQ.js index 9d72e88e44..23808df981 100644 --- a/Common/sources/taskqueueRabbitMQ.js +++ b/Common/sources/taskqueueRabbitMQ.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -38,60 +38,54 @@ var co = require('co'); var utils = require('./utils'); var constants = require('./constants'); var rabbitMQCore = require('./rabbitMQCore'); +var activeMQCore = require('./activeMQCore'); +const commonDefines = require('./commondefines'); +const operationContext = require('./operationContext'); +const cfgMaxRedeliveredCount = config.get('FileConverter.converter.maxRedeliveredCount'); +const cfgQueueType = config.get('queue.type'); var cfgVisibilityTimeout = config.get('queue.visibilityTimeout'); var cfgQueueRetentionPeriod = config.get('queue.retentionPeriod'); var cfgRabbitQueueConvertTask = config.get('rabbitmq.queueconverttask'); var cfgRabbitQueueConvertResponse = config.get('rabbitmq.queueconvertresponse'); var cfgRabbitExchangeConvertDead = config.get('rabbitmq.exchangeconvertdead'); var cfgRabbitQueueConvertDead = config.get('rabbitmq.queueconvertdead'); +var cfgRabbitQueueDelayed = config.get('rabbitmq.queuedelayed'); +var cfgActiveQueueConvertTask = constants.ACTIVEMQ_QUEUE_PREFIX + config.get('activemq.queueconverttask'); +var cfgActiveQueueConvertResponse = constants.ACTIVEMQ_QUEUE_PREFIX + config.get('activemq.queueconvertresponse'); +var cfgActiveQueueConvertDead = constants.ACTIVEMQ_QUEUE_PREFIX + config.get('activemq.queueconvertdead'); +var cfgActiveQueueDelayed = constants.ACTIVEMQ_QUEUE_PREFIX + config.get('activemq.queuedelayed'); -function init(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, callback) { +function initRabbit(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, callback) { return co(function* () { var e = null; try { var conn = yield rabbitMQCore.connetPromise(function() { clear(taskqueue); if (!taskqueue.isClose) { - init(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, null); + setTimeout(() => { + init(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, null); + }, rabbitMQCore.RECONNECT_TIMEOUT); } }); taskqueue.connection = conn; var bAssertTaskQueue = false; - var optionsTaskQueue = { - durable: true, - maxPriority: constants.QUEUE_PRIORITY_VERY_HIGH, + let optionsTaskQueueDefault = { messageTtl: cfgQueueRetentionPeriod * 1000, - deadLetterExchange: cfgRabbitExchangeConvertDead + deadLetterExchange: cfgRabbitExchangeConvertDead.name }; + let optionsTaskQueue = {...optionsTaskQueueDefault, ...cfgRabbitQueueConvertTask.options}; if (isAddTask) { - taskqueue.channelConvertDead = yield rabbitMQCore.createChannelPromise(conn); - yield rabbitMQCore.assertExchangePromise(taskqueue.channelConvertDead, cfgRabbitExchangeConvertDead, 'fanout', - {durable: true}); - var queue = yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertDead, cfgRabbitQueueConvertDead, - {durable: true}); - - taskqueue.channelConvertDead.bindQueue(queue, cfgRabbitExchangeConvertDead, ''); - yield rabbitMQCore.consumePromise(taskqueue.channelConvertDead, queue, function(message) { - if (null != taskqueue.channelConvertDead) { - if (message) { - taskqueue.emit('dead', message.content.toString()); - } - taskqueue.channelConvertDead.ack(message); - } - }, {noAck: false}); - taskqueue.channelConvertTask = yield rabbitMQCore.createConfirmChannelPromise(conn); - yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertTask, cfgRabbitQueueConvertTask, + yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertTask, cfgRabbitQueueConvertTask.name, optionsTaskQueue); bAssertTaskQueue = true; } var bAssertResponseQueue = false; - var optionsResponseQueue = {durable: true}; if (isAddResponse) { taskqueue.channelConvertResponse = yield rabbitMQCore.createConfirmChannelPromise(conn); - yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertResponse, cfgRabbitQueueConvertResponse, - optionsResponseQueue); + yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertResponse, cfgRabbitQueueConvertResponse.name, + cfgRabbitQueueConvertResponse.options); bAssertResponseQueue = true; } var optionsReceive = {noAck: false}; @@ -99,29 +93,192 @@ function init(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddRespon taskqueue.channelConvertTaskReceive = yield rabbitMQCore.createChannelPromise(conn); taskqueue.channelConvertTaskReceive.prefetch(1); if (!bAssertTaskQueue) { - yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertTaskReceive, cfgRabbitQueueConvertTask, + yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertTaskReceive, cfgRabbitQueueConvertTask.name, optionsTaskQueue); } - yield rabbitMQCore.consumePromise(taskqueue.channelConvertTaskReceive, cfgRabbitQueueConvertTask, + yield rabbitMQCore.consumePromise(taskqueue.channelConvertTaskReceive, cfgRabbitQueueConvertTask.name, function (message) { - if (message) { - taskqueue.emit('task', message.content.toString(), message); - } + co(function* () { + let ack = function() { + taskqueue.channelConvertTaskReceive && taskqueue.channelConvertTaskReceive.ack(message); + }; + let redelivered = yield* pushBackRedeliveredRabbit(taskqueue, message, ack); + if (!redelivered) { + if (message) { + taskqueue.emit('task', message.content.toString(), ack); + } + } + }); }, optionsReceive); } if (isAddResponseReceive) { taskqueue.channelConvertResponseReceive = yield rabbitMQCore.createChannelPromise(conn); if (!bAssertResponseQueue) { - yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertResponseReceive, cfgRabbitQueueConvertResponse, - optionsResponseQueue); + yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertResponseReceive, cfgRabbitQueueConvertResponse.name, + cfgRabbitQueueConvertResponse.options); } - yield rabbitMQCore.consumePromise(taskqueue.channelConvertResponseReceive, cfgRabbitQueueConvertResponse, + yield rabbitMQCore.consumePromise(taskqueue.channelConvertResponseReceive, cfgRabbitQueueConvertResponse.name, function (message) { if (message) { - taskqueue.emit('response', message.content.toString(), message); + taskqueue.emit('response', message.content.toString(), function() { + taskqueue.channelConvertResponseReceive && taskqueue.channelConvertResponseReceive.ack(message); + }); } }, optionsReceive); } + if (isAddDelayed) { + let optionsDelayedQueueDefault = { + deadLetterExchange: cfgRabbitExchangeConvertDead.name + }; + let optionsDelayedQueue = {...optionsDelayedQueueDefault, ...cfgRabbitQueueDelayed.options}; + taskqueue.channelDelayed = yield rabbitMQCore.createConfirmChannelPromise(conn); + yield rabbitMQCore.assertQueuePromise(taskqueue.channelDelayed, cfgRabbitQueueDelayed.name, optionsDelayedQueue); + } + if (isEmitDead) { + taskqueue.channelConvertDead = yield rabbitMQCore.createChannelPromise(conn); + yield rabbitMQCore.assertExchangePromise(taskqueue.channelConvertDead, cfgRabbitExchangeConvertDead.name, 'fanout', + cfgRabbitExchangeConvertDead.options); + var queue = yield rabbitMQCore.assertQueuePromise(taskqueue.channelConvertDead, cfgRabbitQueueConvertDead.name, cfgRabbitQueueConvertDead.options); + + taskqueue.channelConvertDead.bindQueue(queue, cfgRabbitExchangeConvertDead.name, ''); + yield rabbitMQCore.consumePromise(taskqueue.channelConvertDead, queue, function(message) { + if (null != taskqueue.channelConvertDead) { + if (message) { + taskqueue.emit('dead', message.content.toString(), function() { + taskqueue.channelConvertDead.ack(message); + }); + } + } + }, {noAck: false}); + } + + //process messages received while reconnection time + repeat(taskqueue); + } catch (err) { + e = err; + } + if (callback) { + callback(e); + } + }); +} +function initActive(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, callback) { + return co(function*() { + var e = null; + try { + var conn = yield activeMQCore.connetPromise(function() { + clear(taskqueue); + if (!taskqueue.isClose) { + setTimeout(() => { + init(taskqueue, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, null); + }, activeMQCore.RECONNECT_TIMEOUT); + } + }); + taskqueue.connection = conn; + if (isAddTask) { + //https://github.com/amqp/rhea/issues/251#issuecomment-535076570 + let optionsConvertTask = { + target: { + address: cfgActiveQueueConvertTask, + capabilities: ['queue'] + } + }; + taskqueue.channelConvertTask = yield activeMQCore.openSenderPromise(conn, optionsConvertTask); + initSenderActive(taskqueue.channelConvertTask, taskqueue.channelConvertTaskData); + } + if (isAddResponse) { + let optionsConvertResponse = { + target: { + address: cfgActiveQueueConvertResponse, + capabilities: ['queue'] + } + }; + taskqueue.channelConvertResponse = yield activeMQCore.openSenderPromise(conn, optionsConvertResponse); + initSenderActive(taskqueue.channelConvertResponse, taskqueue.channelConvertResponseData); + } + if (isAddTaskReceive) { + let optionsConvertTask = { + source: { + address: cfgActiveQueueConvertTask, + capabilities: ['queue'] + }, + credit_window: 0, + autoaccept: false + }; + let receiver = yield activeMQCore.openReceiverPromise(conn, optionsConvertTask); + //todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1 + receiver.add_credit(1); + receiver.on("message", function(context) { + co(function*() { + let ack = function() { + context.delivery.accept(); + receiver.add_credit(1); + }; + let redelivered = yield* pushBackRedeliveredActive(taskqueue, context, ack); + if (!redelivered) { + if (context) { + taskqueue.emit('task', context.message.body, ack); + } + } + }); + }); + taskqueue.channelConvertTaskReceive = receiver; + } + if (isAddResponseReceive) { + let optionsConvertResponse = { + source: { + address: cfgActiveQueueConvertResponse, + capabilities: ['queue'] + }, + credit_window: 0, + autoaccept: false + }; + let receiver = yield activeMQCore.openReceiverPromise(conn, optionsConvertResponse); + //todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1 + receiver.add_credit(1); + receiver.on("message", function(context) { + if (context) { + taskqueue.emit('response', context.message.body, function() { + context.delivery.accept(); + receiver.add_credit(1); + }); + } + }); + taskqueue.channelConvertResponseReceive = receiver; + } + if (isAddDelayed) { + let optionsDelayed = { + target: { + address: cfgActiveQueueDelayed, + capabilities: ['queue'] + } + }; + taskqueue.channelDelayed = yield activeMQCore.openSenderPromise(conn, optionsDelayed); + initSenderActive(taskqueue.channelDelayed, taskqueue.channelDelayedData); + } + if (isEmitDead) { + let optionsConvertDead = { + source: { + address: cfgActiveQueueConvertDead, + capabilities: ['queue'] + }, + credit_window: 0, + autoaccept: false + }; + let receiver = yield activeMQCore.openReceiverPromise(conn, optionsConvertDead); + //todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1 + receiver.add_credit(1); + receiver.on("message", function(context) { + if (context) { + taskqueue.emit('dead', context.message.body, function(){ + context.delivery.accept(); + receiver.add_credit(1); + }); + } + }); + taskqueue.channelConvertDead = receiver; + } + //process messages received while reconnection time repeat(taskqueue); } catch (err) { @@ -138,6 +295,51 @@ function clear(taskqueue) { taskqueue.channelConvertDead = null; taskqueue.channelConvertResponse = null; taskqueue.channelConvertResponseReceive = null; + taskqueue.channelDelayed = null; + //todo clear all listeners + taskqueue.channelConvertTaskData = {}; + taskqueue.channelConvertResponseData = {}; + taskqueue.channelDelayedData = {}; +} +function* pushBackRedeliveredRabbit(taskqueue, message, ack) { + if (message?.fields?.redelivered) { + try { + operationContext.global.logger.warn('checkRedelivered redelivered data=%j', message); + //remove current task and add new into tail of queue to remove redelivered flag + let data = message.content.toString(); + let redeliveredCount = message.properties.headers['x-redelivered-count']; + if (!redeliveredCount || redeliveredCount < cfgMaxRedeliveredCount) { + message.properties.headers['x-redelivered-count'] = redeliveredCount ? redeliveredCount + 1 : 1; + yield addTaskString(taskqueue, data, message.properties.priority, undefined, message.properties.headers); + } else if (taskqueue.simulateErrorResponse) { + yield taskqueue.addResponse(taskqueue.simulateErrorResponse(data)); + } + } catch (err) { + operationContext.global.logger.error('checkRedelivered error: %s', err.stack); + } finally{ + ack(); + } + return true; + } + return false; +} +function* pushBackRedeliveredActive(taskqueue, context, ack) { + if (undefined !== context.message.delivery_count) { + operationContext.global.logger.warn('checkRedelivered redelivered data=%j', context.message); + if (context.message.delivery_count > cfgMaxRedeliveredCount) { + try { + if (taskqueue.simulateErrorResponse) { + yield taskqueue.addResponse(taskqueue.simulateErrorResponse(context.message.body)); + } + } catch (err) { + operationContext.global.logger.error('checkRedelivered error: %s', err.stack); + } finally { + ack(); + } + return true; + } + } + return false; } function repeat(taskqueue) { //repeat addTask because they are lost after the reconnection @@ -145,29 +347,156 @@ function repeat(taskqueue) { //acknowledge data after reconnect raises an exception 'PRECONDITION_FAILED - unknown delivery tag' for (var i = 0; i < taskqueue.addTaskStore.length; ++i) { var elem = taskqueue.addTaskStore[i]; - addTask(taskqueue, elem.task, elem.priority, function () {}, elem.expiration); + addTask(taskqueue, elem.task, elem.priority, function () {}, elem.expiration, elem.headers); } taskqueue.addTaskStore.length = 0; + for (var i = 0; i < taskqueue.addDelayedStore.length; ++i) { + var elem = taskqueue.addDelayedStore[i]; + addDelayed(taskqueue, elem.task, elem.ttl, function () {}); + } + taskqueue.addDelayedStore.length = 0; } -function addTask(taskqueue, content, priority, callback, opt_expiration) { +function addTaskRabbit(taskqueue, content, priority, callback, opt_expiration, opt_headers) { var options = {persistent: true, priority: priority}; if (undefined !== opt_expiration) { options.expiration = opt_expiration.toString(); } - taskqueue.channelConvertTask.sendToQueue(cfgRabbitQueueConvertTask, content, options, callback); + if (undefined !== opt_headers) { + options.headers = opt_headers; + } + taskqueue.channelConvertTask.sendToQueue(cfgRabbitQueueConvertTask.name, content, options, callback); +} +function addTaskActive(taskqueue, content, priority, callback, opt_expiration, opt_headers) { + var msg = {durable: true, priority: priority, body: content, ttl: cfgQueueRetentionPeriod * 1000}; + if (undefined !== opt_expiration) { + msg.ttl = opt_expiration; + } + let delivery = taskqueue.channelConvertTask.send(msg); + if (delivery) { + taskqueue.channelConvertTaskData[delivery.id] = callback; + } +} +function addTaskString(taskqueue, task, priority, opt_expiration, opt_headers) { + //todo confirmation mode + return new Promise(function (resolve, reject) { + var content = Buffer.from(task); + if (null != taskqueue.channelConvertTask) { + addTask(taskqueue, content, priority, function (err, ok) { + if (null != err) { + reject(err); + } else { + resolve(); + } + }, opt_expiration, opt_headers); + } else { + taskqueue.addTaskStore.push({task: content, priority: priority, expiration: opt_expiration, headers: opt_headers}); + resolve(); + } + }); } -function addResponse(taskqueue, content, callback) { +function addResponseRabbit(taskqueue, content, callback) { var options = {persistent: true}; - taskqueue.channelConvertResponse.sendToQueue(cfgRabbitQueueConvertResponse, content, options, callback); + taskqueue.channelConvertResponse.sendToQueue(cfgRabbitQueueConvertResponse.name, content, options, callback); +} +function addResponseActive(taskqueue, content, callback) { + var msg = {durable: true, body: content}; + let delivery = taskqueue.channelConvertResponse.send(msg); + if (delivery) { + taskqueue.channelConvertResponseData[delivery.id] = callback; + } +} +function closeRabbit(conn) { + return rabbitMQCore.closePromise(conn); +} +function closeActive(conn) { + return activeMQCore.closePromise(conn); +} +function addDelayedRabbit(taskqueue, content, ttl, callback) { + var options = {persistent: true, expiration: ttl.toString()}; + taskqueue.channelDelayed.sendToQueue(cfgRabbitQueueDelayed.name, content, options, callback); } -function removeTask(taskqueue, data) { - taskqueue.channelConvertTaskReceive.ack(data); +function addDelayedActive(taskqueue, content, ttl, callback) { + var msg = {durable: true, body: content, ttl: ttl}; + let delivery = taskqueue.channelDelayed.send(msg); + if (delivery) { + taskqueue.channelDelayedData[delivery.id] = callback; + } +} + +function healthCheckRabbit(taskqueue) { + return co(function* () { + if (!taskqueue.channelConvertDead) { + return false; + } + const exchange = yield rabbitMQCore.assertExchangePromise(taskqueue.channelConvertDead, cfgRabbitExchangeConvertDead.name, + 'fanout', cfgRabbitExchangeConvertDead.options); + return !!exchange; + }); +} +function healthCheckActive(taskqueue) { + return co(function* () { + //todo better check + if (!taskqueue.connection) { + return false; + } + return taskqueue.connection.is_open(); + }); +} + +function initSenderActive(sender, senderData) { + let processEvent = function (context, res) { + let id = context?.delivery?.id; + let callback = senderData[id]; + if (callback) { + delete senderData[id]; + callback(res); + } + } + + sender.on('accepted', (context) => { + processEvent(context, null); + }); + sender.on('rejected ', (context) => { + const error = context.delivery?.remote_state?.error; + processEvent(context, new Error("[AMQP] message is rejected (error=" + error + ")")); + }); + sender.on('released', (context) => { + const delivery_failed = context.delivery?.remote_state?.delivery_failed; + const undeliverable_here = context.delivery?.remote_state?.undeliverable_here; + const err = new Error("[AMQP] message is released (delivery_failed=" + delivery_failed + ", undeliverable_here=" + undeliverable_here + ")"); + processEvent(context, err); + }); + sender.on('modified ', (context) => { + const delivery_failed = context.delivery?.remote_state?.delivery_failed; + const undeliverable_here = context.delivery?.remote_state?.undeliverable_here; + const err = new Error("[AMQP] message is modified (delivery_failed=" + delivery_failed + ", undeliverable_here=" + undeliverable_here + ")"); + processEvent(context, err); + }); } -function removeResponse(taskqueue, data) { - taskqueue.channelConvertResponseReceive.ack(data); + +let init; +let addTask; +let addResponse; +let close; +let addDelayed; +let healthCheck; +if (commonDefines.c_oAscQueueType.rabbitmq === cfgQueueType) { + init = initRabbit; + addTask = addTaskRabbit; + addResponse = addResponseRabbit; + close = closeRabbit; + addDelayed = addDelayedRabbit; + healthCheck = healthCheckRabbit; +} else { + init = initActive; + addTask = addTaskActive; + addResponse = addResponseActive; + close = closeActive; + addDelayed = addDelayedActive; + healthCheck = healthCheckActive; } -function TaskQueueRabbitMQ() { +function TaskQueueRabbitMQ(simulateErrorResponse) { this.isClose = false; this.connection = null; this.channelConvertTask = null; @@ -175,16 +504,22 @@ function TaskQueueRabbitMQ() { this.channelConvertDead = null; this.channelConvertResponse = null; this.channelConvertResponseReceive = null; + this.channelDelayed = null; this.addTaskStore = []; + this.addDelayedStore = []; + this.channelConvertTaskData = {}; + this.channelConvertResponseData = {}; + this.channelDelayedData = {}; + this.simulateErrorResponse = simulateErrorResponse; } util.inherits(TaskQueueRabbitMQ, events.EventEmitter); -TaskQueueRabbitMQ.prototype.init = function (isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, callback) { - init(this, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, callback); +TaskQueueRabbitMQ.prototype.init = function (isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, callback) { + init(this, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, callback); }; -TaskQueueRabbitMQ.prototype.initPromise = function(isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive) { +TaskQueueRabbitMQ.prototype.initPromise = function(isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed) { var t = this; return new Promise(function(resolve, reject) { - init(t, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, function(err) { + init(t, isAddTask, isAddResponse, isAddTaskReceive, isAddResponseReceive, isEmitDead, isAddDelayed, function(err) { if (err) { reject(err); } else { @@ -193,32 +528,33 @@ TaskQueueRabbitMQ.prototype.initPromise = function(isAddTask, isAddResponse, isA }); }); }; -TaskQueueRabbitMQ.prototype.addTask = function (task, priority, opt_expiration) { - //todo confirmation mode +TaskQueueRabbitMQ.prototype.addTask = function (task, priority, opt_expiration, opt_headers) { + task.setVisibilityTimeout(cfgVisibilityTimeout); + return addTaskString(this, JSON.stringify(task), priority, opt_expiration); +}; +TaskQueueRabbitMQ.prototype.addResponse = function (task) { var t = this; return new Promise(function (resolve, reject) { - task.setVisibilityTimeout(cfgVisibilityTimeout); - var content = new Buffer(JSON.stringify(task)); - if (null != t.channelConvertTask) { - addTask(t, content, priority, function (err, ok) { + var content = Buffer.from(JSON.stringify(task)); + if (null != t.channelConvertResponse) { + addResponse(t, content, function (err, ok) { if (null != err) { reject(err); } else { resolve(); } - }, opt_expiration); + }); } else { - t.addTaskStore.push({task: content, priority: priority, expiration: opt_expiration}); resolve(); } }); }; -TaskQueueRabbitMQ.prototype.addResponse = function (task) { +TaskQueueRabbitMQ.prototype.addDelayed = function (task, ttl) { var t = this; return new Promise(function (resolve, reject) { var content = new Buffer(JSON.stringify(task)); - if (null != t.channelConvertResponse) { - addResponse(t, content, function (err, ok) { + if (null != t.channelDelayed) { + addDelayed(t, content, ttl, function (err, ok) { if (null != err) { reject(err); } else { @@ -226,40 +562,49 @@ TaskQueueRabbitMQ.prototype.addResponse = function (task) { } }); } else { + t.addDelayedStore.push({task: content, ttl: ttl}); resolve(); } }); }; -TaskQueueRabbitMQ.prototype.removeTask = function (data) { - var t = this; - return new Promise(function (resolve, reject) { - if (null != t.channelConvertTaskReceive) { - removeTask(t, data); +TaskQueueRabbitMQ.prototype.close = function () { + let t = this; + return co(function* () { + t.isClose = true; + if (t.channelConvertTask) { + yield close(t.channelConvertTask); } - resolve(); - }); -}; -TaskQueueRabbitMQ.prototype.removeResponse = function (data) { - var t = this; - return new Promise(function (resolve, reject) { - if (null != t.channelConvertResponseReceive) { - removeResponse(t, data); + if (t.channelConvertTaskReceive) { + yield close(t.channelConvertTaskReceive); + } + if (t.channelConvertDead) { + yield close(t.channelConvertDead); + } + if (t.channelConvertResponse) { + yield close(t.channelConvertResponse); } - resolve(); + if (t.channelConvertResponseReceive) { + yield close(t.channelConvertResponseReceive); + } + if (t.channelDelayed) { + yield close(t.channelDelayed); + } + yield close(t.connection); }); }; -TaskQueueRabbitMQ.prototype.close = function () { - var t = this; - this.isClose = true; - return new Promise(function(resolve, reject) { - t.connection.close(function(err) { - if (err) { - reject(err); - } else { - resolve(); - } +TaskQueueRabbitMQ.prototype.closeOrWait = function() { + if (commonDefines.c_oAscQueueType.rabbitmq === cfgQueueType) { + return this.close(); + } else { + //todo remove sleep + //sleep to wait acknowledge + return this.close().then(() => { + return utils.sleep(1000); }); - }); + } +}; +TaskQueueRabbitMQ.prototype.healthCheck = function() { + return healthCheck(this); }; module.exports = TaskQueueRabbitMQ; diff --git a/Common/sources/tenantManager.js b/Common/sources/tenantManager.js new file mode 100644 index 0000000000..703c81d242 --- /dev/null +++ b/Common/sources/tenantManager.js @@ -0,0 +1,428 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const config = require('config'); +const co = require('co'); +const NodeCache = require( "node-cache" ); +const license = require('./../../Common/sources/license'); +const constants = require('./../../Common/sources/constants'); +const commonDefines = require('./../../Common/sources/commondefines'); +const utils = require('./../../Common/sources/utils'); +const { readFile, readdir } = require('fs/promises'); +const path = require('path'); + +const cfgTenantsBaseDomain = config.get('tenants.baseDomain'); +const cfgTenantsBaseDir = config.get('tenants.baseDir'); +const cfgTenantsFilenameSecret = config.get('tenants.filenameSecret'); +const cfgTenantsFilenameLicense = config.get('tenants.filenameLicense'); +const cfgTenantsFilenameConfig = config.get('tenants.filenameConfig'); +const cfgTenantsDefaultTenant = config.get('tenants.defaultTenant'); +const cfgTenantsCache = config.get('tenants.cache'); +const cfgSecretInbox = config.get('services.CoAuthoring.secret.inbox'); +const cfgSecretOutbox = config.get('services.CoAuthoring.secret.outbox'); +const cfgSecretSession = config.get('services.CoAuthoring.secret.session'); + +let licenseInfo; +let licenseOriginal; +let licenseTuple;//to avoid array creating in getTenantLicense + +const c_LM = constants.LICENSE_MODE; + +const nodeCache = new NodeCache(cfgTenantsCache); + +function getDefautTenant() { + return cfgTenantsDefaultTenant; +} +function getTenant(ctx, domain) { + let tenant = getDefautTenant(); + if (domain) { + //remove port + domain = domain.replace(/\:.*$/, ''); + + if (cfgTenantsBaseDomain && domain.endsWith('.' + cfgTenantsBaseDomain)) { + tenant = domain.substring(0, domain.length - cfgTenantsBaseDomain.length - 1); + } else if(cfgTenantsBaseDomain === domain) { + tenant = getDefautTenant(); + } else { + tenant = domain; + } + } + return tenant; +} +async function getAllTenants(ctx) { + let dirList = []; + try { + if (isMultitenantMode(ctx)) { + const entitiesList = await readdir(cfgTenantsBaseDir, { withFileTypes: true }); + dirList = entitiesList.filter(direntObj => direntObj.isDirectory()).map(directory => directory.name); + } + } catch (error) { + ctx.logger.error('getAllTenants error: ', error.stack); + } + return dirList; +} +function getTenantByConnection(ctx, conn) { + return isMultitenantMode(ctx) ? getTenant(ctx, utils.getDomainByConnection(ctx, conn)) : getDefautTenant(); +} +function getTenantByRequest(ctx, req) { + return isMultitenantMode(ctx) ? getTenant(ctx, utils.getDomainByRequest(ctx, req)) : getDefautTenant(); +} +function getTenantPathPrefix(ctx) { + return isMultitenantMode(ctx) ? utils.removeIllegalCharacters(ctx.tenant) + '/' : ''; +} +async function getTenantConfig(ctx) { + let res = null; + if (isMultitenantMode(ctx) && !isDefaultTenant(ctx)) { + let tenantPath = utils.removeIllegalCharacters(ctx.tenant); + let configPath = path.join(cfgTenantsBaseDir, tenantPath, cfgTenantsFilenameConfig); + res = nodeCache.get(configPath); + if (res) { + ctx.logger.debug('getTenantConfig from cache'); + } else { + try { + let cfgString = await readFile(configPath, {encoding: 'utf8'}); + res = config.util.parseString(cfgString, path.extname(configPath).substring(1)); + ctx.logger.debug('getTenantConfig from %s', configPath); + } catch (e) { + ctx.logger.debug('getTenantConfig error: %s', e.stack); + } finally { + nodeCache.set(configPath, res); + } + } + } + return res; +} +function getTenantSecret(ctx, type) { + return co(function*() { + let cfgTenant; + //check config + switch (type) { + case commonDefines.c_oAscSecretType.Browser: + case commonDefines.c_oAscSecretType.Inbox: + cfgTenant = ctx.getCfg('services.CoAuthoring.secret.inbox', undefined); + break; + case commonDefines.c_oAscSecretType.Outbox: + cfgTenant = ctx.getCfg('services.CoAuthoring.secret.outbox', undefined); + break; + case commonDefines.c_oAscSecretType.Session: + cfgTenant = ctx.getCfg('services.CoAuthoring.secret.session', undefined); + break; + } + if (undefined !== cfgTenant) { + return utils.getSecretByElem(cfgTenant); + } + let res = undefined; + //read secret file + if (isMultitenantMode(ctx) && !isDefaultTenant(ctx)) { + let tenantPath = utils.removeIllegalCharacters(ctx.tenant); + let secretPath = path.join(cfgTenantsBaseDir, tenantPath, cfgTenantsFilenameSecret); + res = nodeCache.get(secretPath); + if (res) { + ctx.logger.debug('getTenantSecret from cache'); + } else { + try { + let secret = yield readFile(secretPath, {encoding: 'utf8'}); + //trim whitespace plus line terminators from string (newline is common on Posix systems) + res = secret.trim(); + if (res.length !== secret.length) { + ctx.logger.warn('getTenantSecret secret in %s contains a leading or trailing whitespace that has been trimmed', secretPath); + } + ctx.logger.debug('getTenantSecret from %s', secretPath); + } catch (e) { + res = undefined; + ctx.logger.warn('getTenantConfig error: %s', e.stack); + } finally { + nodeCache.set(secretPath, res); + } + } + } else { + switch (type) { + case commonDefines.c_oAscSecretType.Browser: + case commonDefines.c_oAscSecretType.Inbox: + res = utils.getSecretByElem(cfgSecretInbox); + break; + case commonDefines.c_oAscSecretType.Outbox: + res = utils.getSecretByElem(cfgSecretOutbox); + break; + case commonDefines.c_oAscSecretType.Session: + res = utils.getSecretByElem(cfgSecretSession); + break; + } + } + return res; + }); +} + +function setDefLicense(data, original) { + licenseInfo = data; + licenseOriginal = original; + licenseTuple = [licenseInfo, licenseOriginal]; +} +//todo move to license file? +function fixTenantLicense(ctx, licenseInfo, licenseInfoTenant) { + let errors = []; + //bitwise + if (0 !== (licenseInfo.mode & c_LM.Limited) && 0 === (licenseInfoTenant.mode & c_LM.Limited)) { + licenseInfoTenant.mode |= c_LM.Limited; + errors.push('timelimited'); + } + if (0 !== (licenseInfo.mode & c_LM.Trial) && 0 === (licenseInfoTenant.mode & c_LM.Trial)) { + licenseInfoTenant.mode |= c_LM.Trial; + errors.push('trial'); + } + if (0 !== (licenseInfo.mode & c_LM.Developer) && 0 === (licenseInfoTenant.mode & c_LM.Developer)) { + licenseInfoTenant.mode |= c_LM.Developer; + errors.push('developer'); + } + //can not turn on + let flags = ['branding', 'customization']; + flags.forEach((flag) => { + if (!licenseInfo[flag] && licenseInfoTenant[flag]) { + licenseInfoTenant[flag] = licenseInfo[flag]; + errors.push(flag); + } + }); + if (!licenseInfo.advancedApi && licenseInfoTenant.advancedApi) { + licenseInfoTenant.advancedApi = licenseInfo.advancedApi; + errors.push('advanced_api'); + } + //can not up limits + // if (licenseInfo.connections < licenseInfoTenant.connections) { + // licenseInfoTenant.connections = licenseInfo.connections; + // errors.push('connections'); + // } + // if (licenseInfo.connectionsView < licenseInfoTenant.connectionsView) { + // licenseInfoTenant.connectionsView = licenseInfo.connectionsView; + // errors.push('connections_view'); + // } + // if (licenseInfo.usersCount < licenseInfoTenant.usersCount) { + // licenseInfoTenant.usersCount = licenseInfo.usersCount; + // errors.push('users_count'); + // } + // if (licenseInfo.usersViewCount < licenseInfoTenant.usersViewCount) { + // licenseInfoTenant.usersViewCount = licenseInfo.usersViewCount; + // errors.push('users_view_count'); + // } + if (licenseInfo.endDate && licenseInfoTenant.endDate && licenseInfo.endDate < licenseInfoTenant.endDate) { + licenseInfoTenant.endDate = licenseInfo.endDate; + errors.push('end_date'); + } + if (errors.length > 0) { + ctx.logger.warn('fixTenantLicense not allowed to improve these license fields: %s', errors.join(', ')); + } +} + +async function getTenantLicense(ctx) { + let res = licenseTuple; + if (isMultitenantMode(ctx) && !isDefaultTenant(ctx)) { + //todo alias is deprecated. remove one year after 8.3 + if (licenseInfo.multitenancy || licenseInfo.alias) { + let tenantPath = utils.removeIllegalCharacters(ctx.tenant); + let licensePath = path.join(cfgTenantsBaseDir, tenantPath, cfgTenantsFilenameLicense); + let licenseTupleTenant = nodeCache.get(licensePath); + if (licenseTupleTenant) { + ctx.logger.debug('getTenantLicense from cache'); + } else { + licenseTupleTenant = await readLicenseTenant(ctx, licensePath, licenseInfo); + fixTenantLicense(ctx, licenseInfo, licenseTupleTenant[0]); + nodeCache.set(licensePath, licenseTupleTenant); + ctx.logger.debug('getTenantLicense from %s', licensePath); + } + res = licenseTupleTenant; + } else { + res = [...res]; + res[0] = {...res[0]}; + res.type = constants.LICENSE_RESULT.Error; + ctx.logger.error('getTenantLicense error: missing "multitenancy" or "alias" field'); + } + } + return res; +} +function getServerLicense(ctx) { + return licenseInfo; +} +let hasBaseDir = !!cfgTenantsBaseDir; +function isMultitenantMode(ctx) { + return hasBaseDir; +} +function setMultitenantMode(val) { + //for tests only!! + return hasBaseDir = val; +} +function isDefaultTenant(ctx) { + return ctx.tenant === cfgTenantsDefaultTenant; +} +//todo move to license file? +async function readLicenseTenant(ctx, licenseFile, baseVerifiedLicense) { + const c_LR = constants.LICENSE_RESULT; + const c_LM = constants.LICENSE_MODE; + let res = {...baseVerifiedLicense}; + let oLicense = null; + try { + const oFile = (await readFile(licenseFile)).toString(); + res.hasLicense = true; + oLicense = JSON.parse(oFile); + //do not verify tenant signature. verify main lic signature. + //delete from object to keep signature secret + delete oLicense['signature']; + if (oLicense['start_date']) { + res.startDate = new Date(oLicense['start_date']); + } + const startDate = res.startDate; + if (oLicense['end_date']) { + res.endDate = new Date(oLicense['end_date']); + } else { + //spread copy do not copy date + res.endDate = new Date(res.endDate); + } + + if (oLicense['customer_id']) { + res.customerId = oLicense['customer_id'] + } + + if (oLicense['alias']) { + res.alias = oLicense['alias']; + } + + if (oLicense['multitenancy']) { + res.multitenancy = oLicense['multitenancy']; + } + + if (true === oLicense['timelimited']) { + res.mode |= c_LM.Limited; + } + if (oLicense.hasOwnProperty('trial')) { + res.mode |= ((true === oLicense['trial'] || 'true' === oLicense['trial'] || 'True' === oLicense['trial']) ? c_LM.Trial : c_LM.None); // Someone who likes to put json string instead of bool + } + if (true === oLicense['developer']) { + res.mode |= c_LM.Developer; + } + if (oLicense.hasOwnProperty('branding')) { + res.branding = (true === oLicense['branding'] || 'true' === oLicense['branding'] || 'True' === oLicense['branding']); // Someone who likes to put json string instead of bool + } + if (oLicense.hasOwnProperty('customization')) { + res.customization = !!oLicense['customization']; + } + if (oLicense.hasOwnProperty('advanced_api')) { + res.advancedApi = !!oLicense['advanced_api']; + } + if (oLicense.hasOwnProperty('connections')) { + res.connections = oLicense['connections'] >> 0; + } + if (oLicense.hasOwnProperty('connections_view')) { + res.connectionsView = oLicense['connections_view'] >> 0; + } + if (oLicense.hasOwnProperty('users_count')) { + res.usersCount = oLicense['users_count'] >> 0; + } + if (oLicense.hasOwnProperty('users_view_count')) { + res.usersViewCount = oLicense['users_view_count'] >> 0; + } + if (oLicense.hasOwnProperty('users_expire')) { + res.usersExpire = Math.max(constants.LICENSE_EXPIRE_USERS_ONE_DAY, (oLicense['users_expire'] >> 0) * + constants.LICENSE_EXPIRE_USERS_ONE_DAY); + } + + const timeLimited = 0 !== (res.mode & c_LM.Limited); + + const checkDate = ((res.mode & c_LM.Trial) || timeLimited) ? new Date() : licenseInfo.buildDate; + //Calendar check of start_date allows to issue a license for old versions + const checkStartDate = new Date(); + if (startDate <= checkStartDate && checkDate <= res.endDate) { + res.type = c_LR.Success; + } else if (startDate > checkStartDate) { + res.type = c_LR.NotBefore; + ctx.logger.warn('License: License not active before start_date:%s.', startDate.toISOString()); + } else if (timeLimited) { + // 30 days after end license = limited mode with 20 Connections + if (res.endDate.setUTCDate(res.endDate.getUTCDate() + 30) >= checkDate) { + res.type = c_LR.SuccessLimit; + res.connections = Math.min(res.connections, constants.LICENSE_CONNECTIONS); + res.connectionsView = Math.min(res.connectionsView, constants.LICENSE_CONNECTIONS); + res.usersCount = Math.min(res.usersCount, constants.LICENSE_USERS); + res.usersViewCount = Math.min(res.usersViewCount, constants.LICENSE_USERS); + let errStr = res.usersCount ? `${res.usersCount} unique users` : `${res.connections} concurrent connections`; + ctx.logger.error(`License: License needs to be renewed.\nYour users have only ${errStr} ` + + `available for document editing for the next 30 days.\nPlease renew the ` + + 'license to restore the full access'); + } else { + res.type = c_LR.ExpiredLimited; + } + } else if (0 !== (res.mode & c_LM.Trial)) { + res.type = c_LR.ExpiredTrial; + } else { + res.type = c_LR.Expired; + } + } catch (e) { + ctx.logger.warn(e); + res.count = 1; + res.connections = 0; + res.connectionsView = 0; + res.usersCount = 0; + res.usersViewCount = 0; + res.type = c_LR.Error; + } + if (res.type === c_LR.Expired || res.type === c_LR.ExpiredLimited || res.type === c_LR.ExpiredTrial) { + res.count = 1; + + let errorMessage; + if (res.type === c_LR.Expired) { + errorMessage = 'Your access to updates and support has expired.\n' + + 'Your license key can not be applied to new versions.\n' + + 'Please extend the license to get updates and support.'; + } else if (res.type === c_LR.ExpiredLimited) { + errorMessage = 'License expired.\nYour users can not edit or view document anymore.\n' + + 'Please renew the license.'; + } else { + errorMessage ='License Expired!!!'; + } + ctx.logger.warn('License: ' + errorMessage); + } + + return [res, oLicense]; +} + +exports.getAllTenants = getAllTenants; +exports.getDefautTenant = getDefautTenant; +exports.getTenantByConnection = getTenantByConnection; +exports.getTenantByRequest = getTenantByRequest; +exports.getTenantPathPrefix = getTenantPathPrefix; +exports.getTenantConfig = getTenantConfig; +exports.getTenantSecret = getTenantSecret; +exports.getTenantLicense = getTenantLicense; +exports.getServerLicense = getServerLicense; +exports.setDefLicense = setDefLicense; +exports.isMultitenantMode = isMultitenantMode; +exports.setMultitenantMode = setMultitenantMode; +exports.isDefaultTenant = isDefaultTenant; diff --git a/Common/sources/utils.js b/Common/sources/utils.js index 70fb5c6715..aa64dffa6a 100644 --- a/Common/sources/utils.js +++ b/Common/sources/utils.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,58 +32,93 @@ 'use strict'; +//Fix EPROTO error in node 8.x at some web sites(https://github.com/nodejs/node/issues/21513) +require("tls").DEFAULT_ECDH_CURVE = "auto"; + +const { pipeline } = require('node:stream/promises'); var config = require('config'); var fs = require('fs'); var path = require('path'); +const crypto = require('crypto'); var url = require('url'); var request = require('request'); var co = require('co'); var URI = require("uri-js"); const escapeStringRegexp = require('escape-string-regexp'); const ipaddr = require('ipaddr.js'); -var configDnsCache = config.get('dnscache'); -const dnscache = require('dnscache')({ - "enable": configDnsCache.get('enable'), - "ttl": configDnsCache.get('ttl'), - "cachesize": configDnsCache.get('cachesize'), - }); +const getDnsCache = require('dnscache'); const jwt = require('jsonwebtoken'); const NodeCache = require( "node-cache" ); const ms = require('ms'); const constants = require('./constants'); +const commonDefines = require('./commondefines'); const forwarded = require('forwarded'); +const { RequestFilteringHttpAgent, RequestFilteringHttpsAgent } = require("request-filtering-agent"); +const https = require('https'); +const ca = require('win-ca/api'); +const util = require('util'); + +const contentDisposition = require('content-disposition'); +const operationContext = require("./operationContext"); + +const cfgDnsCache = config.get('dnscache'); +const cfgIpFilterRules = config.get('services.CoAuthoring.ipfilter.rules'); +const cfgIpFilterErrorCode = config.get('services.CoAuthoring.ipfilter.errorcode'); +const cfgIpFilterUseForRequest = config.get('services.CoAuthoring.ipfilter.useforrequest'); +const cfgExpPemStdTtl = config.get('services.CoAuthoring.expire.pemStdTTL'); +const cfgExpPemCheckPeriod = config.get('services.CoAuthoring.expire.pemCheckPeriod'); +const cfgTokenOutboxHeader = config.get('services.CoAuthoring.token.outbox.header'); +const cfgTokenOutboxPrefix = config.get('services.CoAuthoring.token.outbox.prefix'); +const cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm'); +const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires'); +const cfgVisibilityTimeout = config.get('queue.visibilityTimeout'); +const cfgQueueRetentionPeriod = config.get('queue.retentionPeriod'); +const cfgRequestDefaults = config.get('services.CoAuthoring.requestDefaults'); +const cfgTokenEnableRequestOutbox = config.get('services.CoAuthoring.token.enable.request.outbox'); +const cfgTokenOutboxUrlExclusionRegex = config.get('services.CoAuthoring.token.outbox.urlExclusionRegex'); +const cfgSecret = config.get('aesEncrypt.secret'); +const cfgAESConfig = config.get('aesEncrypt.config'); +const cfgRequesFilteringAgent = config.get('services.CoAuthoring.request-filtering-agent'); +const cfgStorageExternalHost = config.get('storage.externalHost'); +const cfgExternalRequestDirectIfIn = config.get('externalRequest.directIfIn'); +const cfgExternalRequestAction = config.get('externalRequest.action'); +const cfgWinCa = config.get('win-ca'); + +ca(cfgWinCa); -var configIpFilter = config.get('services.CoAuthoring.ipfilter'); -var cfgIpFilterRules = configIpFilter.get('rules'); -var cfgIpFilterErrorCode = configIpFilter.get('errorcode'); -const cfgIpFilterEseForRequest = configIpFilter.get('useforrequest'); -var cfgExpPemStdTtl = config.get('services.CoAuthoring.expire.pemStdTTL'); -var cfgExpPemCheckPeriod = config.get('services.CoAuthoring.expire.pemCheckPeriod'); -var cfgTokenOutboxHeader = config.get('services.CoAuthoring.token.outbox.header'); -var cfgTokenOutboxPrefix = config.get('services.CoAuthoring.token.outbox.prefix'); -var cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm'); -var cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires'); -var cfgSignatureSecretInbox = config.get('services.CoAuthoring.secret.inbox'); -var cfgSignatureSecretOutbox = config.get('services.CoAuthoring.secret.outbox'); -var cfgVisibilityTimeout = config.get('queue.visibilityTimeout'); -var cfgQueueRetentionPeriod = config.get('queue.retentionPeriod'); +const minimumIterationsByteLength = 4; +const dnscache = getDnsCache(cfgDnsCache); var ANDROID_SAFE_FILENAME = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ._-+,@£$€!½§~\'=()[]{}0123456789'; -var g_oIpFilterRules = function() { - var res = []; - for (var i = 0; i < cfgIpFilterRules.length; ++i) { - var rule = cfgIpFilterRules[i]; - var regExpStr = rule['address'].split('*').map(escapeStringRegexp).join('.*'); - var exp = new RegExp('^' + regExpStr + '$', 'i'); - res.push({allow: rule['allowed'], exp: exp}); +//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#use_within_json +BigInt.prototype.toJSON = function() { return this.toString() }; + +var g_oIpFilterRules = new Map(); +function getIpFilterRule(address) { + let exp = g_oIpFilterRules.get(address); + if (!exp) { + let regExpStr = address.split('*').map(escapeStringRegexp).join('.*'); + exp = new RegExp('^' + regExpStr + '$', 'i'); + g_oIpFilterRules.set(address, exp); } - return res; -}(); -var isEmptySecretTenants = isEmptyObject(cfgSignatureSecretInbox.tenants); + return exp; +} const pemfileCache = new NodeCache({stdTTL: ms(cfgExpPemStdTtl) / 1000, checkperiod: ms(cfgExpPemCheckPeriod) / 1000, errorOnMissing: false, useClones: true}); -exports.CONVERTION_TIMEOUT = 1.5 * (cfgVisibilityTimeout + cfgQueueRetentionPeriod) * 1000; +function getRequestFilterAgent(url, options) { + return url.startsWith("https") ? new RequestFilteringHttpsAgent(options) : new RequestFilteringHttpAgent(options); +} + +exports.getConvertionTimeout = function(opt_ctx) { + if (opt_ctx) { + const tenVisibilityTimeout = opt_ctx.getCfg('queue.visibilityTimeout', cfgVisibilityTimeout); + const tenQueueRetentionPeriod = opt_ctx.getCfg('queue.retentionPeriod', cfgQueueRetentionPeriod); + return 1.5 * (tenVisibilityTimeout + tenQueueRetentionPeriod) * 1000; + } else { + return 1.5 * (cfgVisibilityTimeout + cfgQueueRetentionPeriod) * 1000; + } +} exports.addSeconds = function(date, sec) { date.setSeconds(date.getSeconds() + sec); @@ -92,11 +127,19 @@ exports.getMillisecondsOfHour = function(date) { return (date.getUTCMinutes() * 60 + date.getUTCSeconds()) * 1000 + date.getUTCMilliseconds(); }; exports.encodeXml = function(value) { - return value.replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"') - .replace(/'/g, '''); + return value.replace(/[<>&'"\r\n\t\xA0]/g, function (c) { + switch (c) { + case '<': return '<'; + case '>': return '>'; + case '&': return '&'; + case '\'': return '''; + case '"': return '"'; + case '\r': return ' '; + case '\n': return ' '; + case '\t': return ' '; + case '\xA0': return ' '; + } + }); }; function fsStat(fsPath) { return new Promise(function(resolve, reject) { @@ -125,7 +168,16 @@ function* walkDir(fsPath, results, optNoSubDir, optOnlyFolders) { const list = yield fsReadDir(fsPath); for (let i = 0; i < list.length; ++i) { const file = path.join(fsPath, list[i]); - const stats = yield fsStat(file); + let stats; + try { + stats = yield fsStat(file); + } catch (e) { + //exception if fsPath not exist + stats = null; + } + if (!stats) { + continue; + } if (stats.isDirectory()) { if (optNoSubDir) { optOnlyFolders && results.push(file); @@ -206,97 +258,278 @@ function encodeRFC5987ValueChars(str) { replace(/%(?:7C|60|5E)/g, unescape); } function getContentDisposition (opt_filename, opt_useragent, opt_type) { - //from http://stackoverflow.com/questions/93551/how-to-encode-the-filename-parameter-of-content-disposition-header-in-http - var contentDisposition = opt_type ? opt_type : constants.CONTENT_DISPOSITION_ATTACHMENT; - if (opt_filename) { - contentDisposition += '; filename="'; - if (opt_useragent != null && -1 != opt_useragent.toLowerCase().indexOf('android')) { - contentDisposition += makeAndroidSafeFileName(opt_filename) + '"'; - } else { - contentDisposition += opt_filename + '"; filename*=UTF-8\'\'' + encodeRFC5987ValueChars(opt_filename); - } + let type = opt_type || constants.CONTENT_DISPOSITION_ATTACHMENT; + return contentDisposition(opt_filename, {type: type}); +} +exports.getContentDisposition = getContentDisposition; +function raiseError(ro, code, msg) { + ro.abort(); + let error = new Error(msg); + error.code = code; + ro.emit('error', error); +} +function raiseErrorObj(ro, error) { + ro.abort(); + ro.emit('error', error); +} +function isRedirectResponse(response) { + return response && response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location'); +} + +function isAllowDirectRequest(ctx, uri, isInJwtToken) { + let res = false; + const tenExternalRequestDirectIfIn = ctx.getCfg('externalRequest.directIfIn', cfgExternalRequestDirectIfIn); + let allowList = tenExternalRequestDirectIfIn.allowList; + if (allowList.length > 0) { + let allowIndex = allowList.findIndex((allowPrefix) => { + return uri.startsWith(allowPrefix); + }, uri); + res = -1 !== allowIndex; + ctx.logger.debug("isAllowDirectRequest check allow list res=%s", res); + } else if (tenExternalRequestDirectIfIn.jwtToken) { + res = isInJwtToken; + ctx.logger.debug("isAllowDirectRequest url in jwt token res=%s", res); } - return contentDisposition; -} -function getContentDispositionS3 (opt_filename, opt_useragent, opt_type) { - var contentDisposition = opt_type ? opt_type : constants.CONTENT_DISPOSITION_ATTACHMENT; - if (opt_filename) { - contentDisposition += ';'; - if (opt_useragent != null && -1 != opt_useragent.toLowerCase().indexOf('android')) { - contentDisposition += ' filename=' + makeAndroidSafeFileName(opt_filename); - } else { - if (containsAllAsciiNP(opt_filename)) { - contentDisposition += ' filename=' + opt_filename; - } else { - contentDisposition += ' filename*=UTF-8\'\'' + encodeRFC5987ValueChars(opt_filename); + return res; +} +function addExternalRequestOptions(ctx, uri, isInJwtToken, options) { + let res = false; + const tenExternalRequestAction = ctx.getCfg('externalRequest.action', cfgExternalRequestAction); + const tenRequesFilteringAgent = ctx.getCfg('services.CoAuthoring.request-filtering-agent', cfgRequesFilteringAgent); + if (isAllowDirectRequest(ctx, uri, isInJwtToken)) { + res = true; + } else if (tenExternalRequestAction.allow) { + res = true; + if (tenExternalRequestAction.blockPrivateIP) { + const agentOptions = Object.assign({}, https.globalAgent.options, tenRequesFilteringAgent); + options.agent = getRequestFilterAgent(uri, agentOptions); + } + if (tenExternalRequestAction.proxyUrl) { + options.proxy = tenExternalRequestAction.proxyUrl; + } + if (tenExternalRequestAction.proxyUser?.username) { + let user = tenExternalRequestAction.proxyUser.username; + let pass = tenExternalRequestAction.proxyUser.password; + options.headers = {'proxy-authorization': `${user}:${pass}`}; + } + if (tenExternalRequestAction.proxyHeaders) { + if (!options.headers) { + options.headers = {}; } + Object.assign(options.headers, tenExternalRequestAction.proxyHeaders); } } - return contentDisposition; + return res; } -exports.getContentDisposition = getContentDisposition; -exports.getContentDispositionS3 = getContentDispositionS3; -function downloadUrlPromise(uri, optTimeout, optLimit, opt_Authorization) { + +function downloadUrlPromise(ctx, uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) { + //todo replace deprecated request module + const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults); + const maxRedirects = (undefined !== tenTenantRequestDefaults.maxRedirects) ? tenTenantRequestDefaults.maxRedirects : 10; + const followRedirect = (undefined !== tenTenantRequestDefaults.followRedirect) ? tenTenantRequestDefaults.followRedirect : true; + var redirectsFollowed = 0; + let doRequest = function(curUrl) { + return downloadUrlPromiseWithoutRedirect(ctx, curUrl, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) + .catch(function(err) { + let response = err.response; + if (isRedirectResponse(response)) { + let redirectTo = response.caseless.get('location'); + if (followRedirect && redirectsFollowed < maxRedirects) { + if (!/^https?:/.test(redirectTo) && err.request) { + redirectTo = url.resolve(err.request.uri.href, redirectTo) + } + + ctx.logger.debug('downloadUrlPromise redirectsFollowed:%d redirectTo: %s', redirectsFollowed, redirectTo); + redirectsFollowed++; + return doRequest(redirectTo); + } + } + throw err; + }); + }; + return doRequest(uri); +} +function downloadUrlPromiseWithoutRedirect(ctx, uri, optTimeout, optLimit, opt_Authorization, opt_filterPrivate, opt_headers, opt_streamWriter) { return new Promise(function (resolve, reject) { + const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults); + const tenTokenOutboxHeader = ctx.getCfg('services.CoAuthoring.token.outbox.header', cfgTokenOutboxHeader); + const tenTokenOutboxPrefix = ctx.getCfg('services.CoAuthoring.token.outbox.prefix', cfgTokenOutboxPrefix); //IRI to URI uri = URI.serialize(URI.parse(uri)); var urlParsed = url.parse(uri); + let sizeLimit = optLimit || Number.MAX_VALUE; + let bufferLength = 0, timeoutId; + let hash = crypto.createHash('sha256'); //if you expect binary data, you should set encoding: null - var options = {uri: urlParsed, encoding: null, timeout: optTimeout}; - if (opt_Authorization) { - options.headers = {}; - options.headers[cfgTokenOutboxHeader] = cfgTokenOutboxPrefix + opt_Authorization; + let connectionAndInactivity = optTimeout && optTimeout.connectionAndInactivity && ms(optTimeout.connectionAndInactivity); + let options = config.util.extendDeep({}, tenTenantRequestDefaults); + Object.assign(options, {uri: urlParsed, encoding: null, timeout: connectionAndInactivity, followRedirect: false}); + if (!addExternalRequestOptions(ctx, uri, opt_filterPrivate, options)) { + reject(new Error('Block external request. See externalRequest config options')); + return; } - //TODO: Check how to correct handle a ssl link - urlParsed.rejectUnauthorized = false; - options.rejectUnauthorized = false; - request.get(options, function (err, response, body) { - if (err) { - reject(err); - } else { - var correctSize = (!optLimit || body.length < optLimit); - if (response.statusCode == 200 && correctSize) { - resolve(body); + if (!options.agent) { + //baseRequest creates new agent(win-ca injects in globalAgent) + options.agentOptions = https.globalAgent.options; + } + if (!options.headers) { + options.headers = {}; + } + if (opt_Authorization) { + options.headers[tenTokenOutboxHeader] = tenTokenOutboxPrefix + opt_Authorization; + } + if (opt_headers) { + Object.assign(options.headers, opt_headers); + } + let fError = function(err) { + clearTimeout(timeoutId); + reject(err); + } + if (!opt_streamWriter) { + fError = function() {}; + let executed = false; + options.callback = function(err, response, body) { + if (executed) { + return; + } + executed = true; + if (err) { + clearTimeout(timeoutId); + reject(err); } else { - if (!correctSize) { - var e = new Error('Error response: statusCode:' + response.statusCode + ' ;body.length:' + body.length); - e.code = 'EMSGSIZE'; - reject(e); - } else { - reject(new Error('Error response: statusCode:' + response.statusCode + ' ;body:\r\n' + body)); + var contentLength = response.caseless.get('content-length'); + if (contentLength && body.length !== (contentLength - 0)) { + ctx.logger.warn('downloadUrlPromise body size mismatch: uri=%s; content-length=%s; body.length=%d', uri, contentLength, body.length); } + let sha256 = hash.digest('hex'); + clearTimeout(timeoutId); + resolve({response: response, body: body, sha256: sha256}); + } + }; + } + let fResponse = function(response) { + if (opt_streamWriter) { + //Set-Cookie resets browser session + response.caseless.del('Set-Cookie'); + } + var contentLength = response.caseless.get('content-length'); + if (contentLength && (contentLength - 0) > sizeLimit) { + raiseError(this, 'EMSGSIZE', 'Error response: content-length:' + contentLength); + } else if (response.statusCode !== 200 && response.statusCode !== 206) { + let code = response.statusCode; + let responseHeaders = JSON.stringify(response.headers); + let error = new Error(`Error response: statusCode:${code}; headers:${responseHeaders};`); + error.statusCode = response.statusCode; + error.request = this; + error.response = response; + if (opt_streamWriter && !isRedirectResponse(response)) { + this.off('error', fError); + pipeline(this, opt_streamWriter) + .then(resolve, reject) + .finally(() => { + clearTimeout(timeoutId); + }); + } else { + raiseErrorObj(this, error); } + } else if (opt_streamWriter) { + this.off('error', fError); + pipeline(this, opt_streamWriter) + .then(resolve, reject) + .finally(() => { + clearTimeout(timeoutId); + }); } - }) + }; + let fData = function(chunk) { + hash.update(chunk); + bufferLength += chunk.length; + if (bufferLength > sizeLimit) { + raiseError(this, 'EMSGSIZE', 'Error response body.length'); + } + } + + let ro = request.get(options) + .on('response', fResponse) + .on('data', fData) + .on('error', fError); + if (optTimeout && optTimeout.wholeCycle) { + timeoutId = setTimeout(function() { + raiseError(ro, 'ETIMEDOUT', `Error: whole request cycle timeout: ${optTimeout.wholeCycle}`); + }, ms(optTimeout.wholeCycle)); + } }); } -function postRequestPromise(uri, postData, optTimeout, opt_Authorization) { +function postRequestPromise(ctx, uri, postData, postDataStream, postDataSize, optTimeout, opt_Authorization, opt_isInJwtToken, opt_headers) { return new Promise(function(resolve, reject) { + const tenTenantRequestDefaults = ctx.getCfg('services.CoAuthoring.requestDefaults', cfgRequestDefaults); + const tenTokenOutboxHeader = ctx.getCfg('services.CoAuthoring.token.outbox.header', cfgTokenOutboxHeader); + const tenTokenOutboxPrefix = ctx.getCfg('services.CoAuthoring.token.outbox.prefix', cfgTokenOutboxPrefix); //IRI to URI uri = URI.serialize(URI.parse(uri)); var urlParsed = url.parse(uri); - var headers = {'Content-Type': 'application/json'}; + let connectionAndInactivity = optTimeout && optTimeout.connectionAndInactivity && ms(optTimeout.connectionAndInactivity); + let options = config.util.extendDeep({}, tenTenantRequestDefaults); + Object.assign(options, {uri: urlParsed, encoding: 'utf8', timeout: connectionAndInactivity}); + if (!addExternalRequestOptions(ctx, uri, opt_isInJwtToken, options)) { + reject(new Error('Block external request. See externalRequest config options')); + return; + } + if (!options.agent) { + //baseRequest creates new agent(win-ca injects in globalAgent) + options.agentOptions = https.globalAgent.options; + } + if (postData) { + options.body = postData; + } + if (!options.headers) { + options.headers = {}; + } if (opt_Authorization) { - headers[cfgTokenOutboxHeader] = cfgTokenOutboxPrefix + opt_Authorization; + //todo ctx.getCfg + options.headers[tenTokenOutboxHeader] = tenTokenOutboxPrefix + opt_Authorization; } - var options = {uri: urlParsed, body: postData, encoding: 'utf8', headers: headers, timeout: optTimeout}; - - //TODO: Check how to correct handle a ssl link - urlParsed.rejectUnauthorized = false; - options.rejectUnauthorized = false; - - request.post(options, function(err, response, body) { + if (opt_headers) { + Object.assign(options.headers, opt_headers); + } + if (undefined !== postDataSize) { + //If no Content-Length is set, data will automatically be encoded in HTTP Chunked transfer encoding, + //so that server knows when the data ends. The Transfer-Encoding: chunked header is added. + //https://nodejs.org/api/http.html#requestwritechunk-encoding-callback + //issue with Transfer-Encoding: chunked wopi and sharepoint 2019 + //https://community.alteryx.com/t5/Dev-Space/Download-Tool-amp-Microsoft-SharePoint-Chunked-Request-Error/td-p/735824 + options.headers['Content-Length'] = postDataSize; + } + let executed = false; + let ro = request.post(options, function(err, response, body) { + if (executed) { + return; + } + executed = true; if (err) { reject(err); } else { - if (200 == response.statusCode || 204 == response.statusCode) { - resolve(body); + if (200 === response.statusCode || 204 === response.statusCode) { + resolve({response: response, body: body}); } else { - reject(new Error('Error response: statusCode:' + response.statusCode + ' ;body:\r\n' + body)); + let code = response.statusCode; + let responseHeaders = JSON.stringify(response.headers); + let error = new Error(`Error response: statusCode:${code}; headers:${responseHeaders}; body:\r\n${body}`); + error.statusCode = response.statusCode; + error.response = response; + reject(error); } } - }) + }); + if (optTimeout && optTimeout.wholeCycle) { + setTimeout(function() { + raiseError(ro, 'ETIMEDOUT', `Error: whole request cycle timeout: ${optTimeout.wholeCycle}`); + }, ms(optTimeout.wholeCycle)); + } + if (postDataStream && !postData) { + postDataStream.pipe(ro); + } }); } exports.postRequestPromise = postRequestPromise; @@ -305,50 +538,48 @@ exports.mapAscServerErrorToOldError = function(error) { var res = -1; switch (error) { case constants.NO_ERROR : + case constants.CONVERT_CELLLIMITS : res = 0; break; case constants.TASK_QUEUE : case constants.TASK_RESULT : res = -6; break; + case constants.CONVERT_PASSWORD : + case constants.CONVERT_DRM : + case constants.CONVERT_DRM_UNSUPPORTED : + res = -5; + break; case constants.CONVERT_DOWNLOAD : res = -4; break; case constants.CONVERT_TIMEOUT : + case constants.CONVERT_DEAD_LETTER : res = -2; break; - case constants.CONVERT_PASSWORD : - case constants.CONVERT_DRM : - case constants.CONVERT_NEED_PARAMS : case constants.CONVERT_PARAMS : + res = -7; + break; + case constants.CONVERT_LIMITS : + res = -10; + break; + case constants.CONVERT_NEED_PARAMS : case constants.CONVERT_LIBREOFFICE : case constants.CONVERT_CORRUPTED : - case constants.CONVERT_MS_OFFCRYPTO : case constants.CONVERT_UNKNOWN_FORMAT : case constants.CONVERT_READ_FILE : + case constants.CONVERT_TEMPORARY : case constants.CONVERT : res = -3; break; - case constants.UPLOAD_CONTENT_LENGTH : + case constants.CONVERT_DETECT : res = -9; break; - case constants.UPLOAD_EXTENSION : - res = -10; - break; - case constants.UPLOAD_COUNT_FILES : - res = -11; - break; case constants.VKEY : - res = -8; - break; case constants.VKEY_ENCRYPT : - res = -20; - break; case constants.VKEY_KEY_EXPIRE : - res = -21; - break; case constants.VKEY_USER_COUNT_EXCEED : - res = -22; + res = -8; break; case constants.STORAGE : case constants.STORAGE_FILE_NO_FOUND : @@ -375,35 +606,71 @@ function fillXmlResponse(val) { } else { xml += ''; } + if (val.fileType) { + xml += '' + exports.encodeXml(val.fileType) + ''; + } else { + xml += ''; + } xml += '' + val.percent + ''; xml += '' + (val.endConvert ? 'True' : 'False') + ''; } xml += ''; return xml; } -function fillResponse(req, res, uri, error) { - var data; - var contentType; - var output; - if (constants.NO_ERROR != error) { - output = {error: exports.mapAscServerErrorToOldError(error)}; - } else { - output = {fileUrl: uri, percent: (uri ? 100 : 0), endConvert: !!uri}; - } - var accept = req.get('Accept'); - if (accept && -1 != accept.toLowerCase().indexOf('application/json')) { + +function fillResponseSimple(res, str, contentType) { + let body = Buffer.from(str, 'utf-8'); + res.setHeader('Content-Type', contentType + '; charset=UTF-8'); + res.setHeader('Content-Length', body.length); + res.send(body); +} +function _fillResponse(res, output, isJSON) { + let data; + let contentType; + if (isJSON) { data = JSON.stringify(output); contentType = 'application/json'; } else { data = fillXmlResponse(output); contentType = 'text/xml'; } - var body = new Buffer(data, 'utf-8'); - res.setHeader('Content-Type', contentType + '; charset=UTF-8'); - res.setHeader('Content-Length', body.length); - res.send(body); + fillResponseSimple(res, data, contentType); +} + +function fillResponse(req, res, convertStatus, isJSON) { + let output; + if (constants.NO_ERROR != convertStatus.err) { + output = {error: exports.mapAscServerErrorToOldError(convertStatus.err)}; + } else { + output = {fileUrl: convertStatus.url, fileType: convertStatus.filetype, percent: (convertStatus.end ? 100 : 0), endConvert: convertStatus.end}; + } + const accepts = isJSON ? ['json', 'xml'] : ['xml', 'json']; + switch (req.accepts(accepts)) { + case 'json': + isJSON = true; + break; + case 'xml': + isJSON = false; + break; + } + _fillResponse(res, output, isJSON); } + +exports.fillResponseSimple = fillResponseSimple; exports.fillResponse = fillResponse; + +function fillResponseBuilder(res, key, urls, end, error) { + let output; + if (constants.NO_ERROR != error) { + output = {error: exports.mapAscServerErrorToOldError(error)}; + } else { + output = {key: key, urls: urls, end: end}; + } + _fillResponse(res, output, true); +} + +exports.fillResponseBuilder = fillResponseBuilder; + function promiseCreateWriteStream(strPath, optOptions) { return new Promise(function(resolve, reject) { var file = fs.createWriteStream(strPath, optOptions); @@ -418,6 +685,21 @@ function promiseCreateWriteStream(strPath, optOptions) { }); }; exports.promiseCreateWriteStream = promiseCreateWriteStream; + +function promiseWaitDrain(stream) { + return new Promise(function(resolve, reject) { + stream.once('drain', resolve); + }); +} +exports.promiseWaitDrain = promiseWaitDrain; + +function promiseWaitClose(stream) { + return new Promise(function(resolve, reject) { + stream.once('close', resolve); + }); +} +exports.promiseWaitClose = promiseWaitClose; + function promiseCreateReadStream(strPath) { return new Promise(function(resolve, reject) { var file = fs.createReadStream(strPath); @@ -448,29 +730,6 @@ exports.compareStringByLength = function(x, y) { } return 0; }; -function makeCRCTable() { - var c; - var crcTable = []; - for (var n = 0; n < 256; n++) { - c = n; - for (var k = 0; k < 8; k++) { - c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); - } - crcTable[n] = c; - } - return crcTable; -} -var crcTable; -exports.crc32 = function(str) { - var crcTable = crcTable || (crcTable = makeCRCTable()); - var crc = 0 ^ (-1); - - for (var i = 0; i < str.length; i++) { - crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF]; - } - - return (crc ^ (-1)) >>> 0; -}; exports.promiseRedis = function(client, func) { var newArguments = Array.prototype.slice.call(arguments, 2); return new Promise(function(resolve, reject) { @@ -491,37 +750,88 @@ function containsAllAsciiNP(str) { return /^[\040-\176]*$/.test(str);//non-printing characters } exports.containsAllAsciiNP = containsAllAsciiNP; -function getBaseUrl(protocol, hostHeader, forwardedProtoHeader, forwardedHostHeader) { +function getDomain(hostHeader, forwardedHostHeader) { + return forwardedHostHeader || hostHeader || 'localhost'; +}; +function getBaseUrl(protocol, hostHeader, forwardedProtoHeader, forwardedHostHeader, forwardedPrefixHeader) { var url = ''; - if (forwardedProtoHeader) { + if (forwardedProtoHeader && constants.ALLOWED_PROTO.test(forwardedProtoHeader)) { url += forwardedProtoHeader; - } else if (protocol) { + } else if (protocol && constants.ALLOWED_PROTO.test(protocol)) { url += protocol; } else { url += 'http'; } url += '://'; - if (forwardedHostHeader) { - url += forwardedHostHeader; - } else if (hostHeader) { - url += hostHeader; - } else { - url += 'localhost'; + url += getDomain(hostHeader, forwardedHostHeader); + if (forwardedPrefixHeader) { + url += forwardedPrefixHeader; } return url; } -function getBaseUrlByConnection(conn) { - return getBaseUrl('', conn.headers['host'], conn.headers['x-forwarded-proto'], conn.headers['x-forwarded-host']); +function getBaseUrlByConnection(ctx, conn) { + conn = conn.request; + //Header names are lower-cased. https://nodejs.org/api/http.html#messageheaders + let cloudfrontForwardedProto = conn.headers['cloudfront-forwarded-proto']; + let forwardedProto = conn.headers['x-forwarded-proto']; + let forwardedHost = conn.headers['x-forwarded-host']; + let forwardedPrefix = conn.headers['x-forwarded-prefix']; + let host = conn.headers['host']; + let proto = cloudfrontForwardedProto || forwardedProto; + ctx.logger.debug(`getBaseUrlByConnection host=%s x-forwarded-host=%s x-forwarded-proto=%s x-forwarded-prefix=%s cloudfront-forwarded-proto=%s `, + host, forwardedHost, forwardedProto, forwardedPrefix, cloudfrontForwardedProto); + return getBaseUrl('', host, proto, forwardedHost, forwardedPrefix); } -function getBaseUrlByRequest(req) { - return getBaseUrl(req.protocol, req.get('host'), req.get('x-forwarded-proto'), req.get('x-forwarded-host')); +function getBaseUrlByRequest(ctx, req) { + //case-insensitive match. https://expressjs.com/en/api.html#req.get + let cloudfrontForwardedProto = req.get('cloudfront-forwarded-proto'); + let forwardedProto = req.get('x-forwarded-proto'); + let forwardedHost = req.get('x-forwarded-host'); + let forwardedPrefix = req.get('x-forwarded-prefix'); + let host = req.get('host'); + let protocol = req.protocol; + let proto = cloudfrontForwardedProto || forwardedProto; + ctx.logger.debug(`getBaseUrlByRequest protocol=%s host=%s x-forwarded-host=%s x-forwarded-proto=%s x-forwarded-prefix=%s cloudfront-forwarded-proto=%s `, + protocol, host, forwardedHost, forwardedProto, forwardedPrefix, cloudfrontForwardedProto); + return getBaseUrl(protocol, host, proto, forwardedHost, forwardedPrefix); } exports.getBaseUrlByConnection = getBaseUrlByConnection; exports.getBaseUrlByRequest = getBaseUrlByRequest; +function getDomainByConnection(ctx, conn) { + let incomingMessage = conn.request; + let host = incomingMessage.headers['host']; + let forwardedHost = incomingMessage.headers['x-forwarded-host']; + ctx.logger.debug("getDomainByConnection headers['host']=%s headers['x-forwarded-host']=%s", host, forwardedHost); + return getDomain(host, forwardedHost); +} +function getDomainByRequest(ctx, req) { + let host = req.get('host'); + let forwardedHost = req.get('x-forwarded-host'); + ctx.logger.debug("getDomainByRequest headers['host']=%s headers['x-forwarded-host']=%s", host, forwardedHost); + return getDomain(req.get('host'), req.get('x-forwarded-host')); +} +exports.getDomainByConnection = getDomainByConnection; +exports.getDomainByRequest = getDomainByRequest; +function getShardKeyByConnection(ctx, conn) { + return conn?.handshake?.query?.[constants.SHARD_KEY_API_NAME]; +} +function getWopiSrcByConnection(ctx, conn) { + return conn?.handshake?.query?.[constants.SHARD_KEY_WOPI_NAME]; +} +function getShardKeyByRequest(ctx, req) { + return req.query?.[constants.SHARD_KEY_API_NAME]; +} +function getWopiSrcByRequest(ctx, req) { + return req.query?.[constants.SHARD_KEY_WOPI_NAME]; +} +exports.getShardKeyByConnection = getShardKeyByConnection; +exports.getWopiSrcByConnection = getWopiSrcByConnection; +exports.getShardKeyByRequest = getShardKeyByRequest; +exports.getWopiSrcByRequest = getWopiSrcByRequest; function stream2Buffer(stream) { return new Promise(function(resolve, reject) { if (!stream.readable) { - resolve(new Buffer()); + resolve(Buffer.alloc(0)); } var bufs = []; stream.on('data', function(data) { @@ -567,13 +877,15 @@ function* pipeFiles(from, to) { yield pipeStreams(fromStream, toStream, true); } exports.pipeFiles = co.wrap(pipeFiles); -function checkIpFilter(ipString, opt_hostname) { +function checkIpFilter(ctx, ipString, opt_hostname) { + const tenIpFilterRules = ctx.getCfg('services.CoAuthoring.ipfilter.rules', cfgIpFilterRules); + var status = 0; var ip4; var ip6; if (ipaddr.isValid(ipString)) { var ip = ipaddr.parse(ipString); - if ('ipv6' == ip.kind()) { + if ('ipv6' === ip.kind()) { if (ip.isIPv4MappedAddress()) { ip4 = ip.toIPv4Address().toString(); } @@ -583,11 +895,14 @@ function checkIpFilter(ipString, opt_hostname) { ip6 = ip.toIPv4MappedAddress().toNormalizedString(); } } - for (var i = 0; i < g_oIpFilterRules.length; ++i) { - var rule = g_oIpFilterRules[i]; - if ((opt_hostname && rule.exp.test(opt_hostname)) || (ip4 && rule.exp.test(ip4)) || (ip6 && rule.exp.test(ip6))) { - if (!rule.allow) { - status = cfgIpFilterErrorCode; + + for (let i = 0; i < tenIpFilterRules.length; ++i) { + let rule = tenIpFilterRules[i]; + let exp = getIpFilterRule(rule.address); + if ((opt_hostname && exp.test(opt_hostname)) || (ip4 && exp.test(ip4)) || (ip6 && exp.test(ip6))) { + if (!rule.allowed) { + const tenIpFilterErrorCode = ctx.getCfg('services.CoAuthoring.ipfilter.errorcode', cfgIpFilterErrorCode); + status = tenIpFilterErrorCode; } break; } @@ -595,12 +910,31 @@ function checkIpFilter(ipString, opt_hostname) { return status; } exports.checkIpFilter = checkIpFilter; +function* checkHostFilter(ctx, hostname) { + let status = 0; + let hostIp; + try { + hostIp = yield dnsLookup(hostname); + } catch (e) { + const tenIpFilterErrorCode = ctx.getCfg('services.CoAuthoring.ipfilter.errorcode', cfgIpFilterErrorCode); + status = tenIpFilterErrorCode; + ctx.logger.error('dnsLookup error: hostname = %s %s', hostname, e.stack); + } + if (0 === status) { + status = checkIpFilter(ctx, hostIp, hostname); + } + return status; +} +exports.checkHostFilter = checkHostFilter; function checkClientIp(req, res, next) { + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + const tenIpFilterUseForRequest = ctx.getCfg('services.CoAuthoring.ipfilter.useforrequest', cfgIpFilterUseForRequest); let status = 0; - if (cfgIpFilterEseForRequest) { + if (tenIpFilterUseForRequest) { const addresses = forwarded(req); const ipString = addresses[addresses.length - 1]; - status = checkIpFilter(ipString); + status = checkIpFilter(ctx, ipString); } if (status > 0) { res.sendStatus(status); @@ -609,6 +943,16 @@ function checkClientIp(req, res, next) { } } exports.checkClientIp = checkClientIp; +function lowercaseQueryString(req, res, next) { + for (var key in req.query) { + if (req.query.hasOwnProperty(key) && key.toLowerCase() !== key) { + req.query[key.toLowerCase()] = req.query[key]; + delete req.query[key]; + } + } + next(); +} +exports.lowercaseQueryString = lowercaseQueryString; function dnsLookup(hostname, options) { return new Promise(function(resolve, reject) { dnscache.lookup(hostname, options, function(err, addresses){ @@ -641,38 +985,291 @@ function getSecretByElem(secretElem) { return secret; } exports.getSecretByElem = getSecretByElem; -function getSecret(docId, opt_iss, opt_token) { - var secretElem = cfgSignatureSecretInbox; - if (!isEmptySecretTenants) { - var iss; - if (opt_token) { - //look for issuer - var decodedTemp = jwt.decode(opt_token); - if (decodedTemp && decodedTemp.iss) { - iss = decodedTemp.iss; - } +function fillJwtForRequest(ctx, payload, secret, opt_inBody) { + const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm); + const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires); + //todo refuse prototypes in payload(they are simple getter/setter). + //JSON.parse/stringify is more universal but Object.assign is enough for our inputs + payload = Object.assign(Object.create(null), payload); + let data; + if (opt_inBody) { + data = payload; + } else { + data = {payload: payload}; + } + + let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires}; + return jwt.sign(data, secret, options); +} +exports.fillJwtForRequest = fillJwtForRequest; +exports.forwarded = forwarded; +exports.getIndexFromUserId = function(userId, userIdOriginal){ + return parseInt(userId.substring(userIdOriginal.length)); +}; +exports.checkPathTraversal = function(ctx, docId, rootDirectory, filename) { + if (filename.indexOf('\0') !== -1) { + ctx.logger.warn('checkPathTraversal Poison Null Bytes filename=%s', filename); + return false; + } + if (!filename.startsWith(rootDirectory)) { + ctx.logger.warn('checkPathTraversal Path Traversal filename=%s', filename); + return false; + } + return true; +}; +exports.getConnectionInfo = function(conn){ + var user = conn.user; + var data = { + id: user.id, + idOriginal: user.idOriginal, + username: user.username, + indexUser: user.indexUser, + view: user.view, + connectionId: conn.id, + isCloseCoAuthoring: conn.isCloseCoAuthoring, + isLiveViewer: exports.isLiveViewer(conn), + encrypted: conn.encrypted + }; + return data; +}; +exports.getConnectionInfoStr = function(conn){ + return JSON.stringify(exports.getConnectionInfo(conn)); +}; +exports.isLiveViewer = function(conn){ + return conn.user?.view && "fast" === conn.coEditingMode; +}; +exports.isLiveViewerSupport = function(licenseInfo){ + return licenseInfo.connectionsView > 0 || licenseInfo.usersViewCount > 0; +}; +exports.canIncludeOutboxAuthorization = function (ctx, url) { + const tenTokenEnableRequestOutbox = ctx.getCfg('services.CoAuthoring.token.enable.request.outbox', cfgTokenEnableRequestOutbox); + const tenTokenOutboxUrlExclusionRegex = ctx.getCfg('services.CoAuthoring.token.outbox.urlExclusionRegex', cfgTokenOutboxUrlExclusionRegex); + if (tenTokenEnableRequestOutbox) { + if (!tenTokenOutboxUrlExclusionRegex) { + return true; + } else if (!new RegExp(escapeStringRegexp(tenTokenOutboxUrlExclusionRegex)).test(url)) { + return true; } else { - iss = opt_iss; + ctx.logger.debug('canIncludeOutboxAuthorization excluded by token.outbox.urlExclusionRegex url=%s', url); } - if (iss) { - secretElem = cfgSignatureSecretInbox.tenants[iss]; - if (!secretElem) { - logger.error('getSecret unknown issuer: docId = %s iss = %s', docId, iss); - } + } + return false; +}; +/* + Code samples taken from here: https://gist.github.com/btxtiger/e8eaee70d6e46729d127f1e384e755d6 + */ +exports.encryptPassword = async function (ctx, password) { + const pbkdf2Promise = util.promisify(crypto.pbkdf2); + const tenSecret = ctx.getCfg('aesEncrypt.secret', cfgSecret); + const tenAESConfig = ctx.getCfg('aesEncrypt.config', cfgAESConfig) ?? {}; + const { + keyByteLength = 32, + saltByteLength = 64, + initializationVectorByteLength = 16, + iterationsByteLength = 5 + } = tenAESConfig; + + const salt = crypto.randomBytes(saltByteLength); + const initializationVector = crypto.randomBytes(initializationVectorByteLength); + + const iterationsLength = iterationsByteLength < minimumIterationsByteLength ? minimumIterationsByteLength : iterationsByteLength; + // Generate random count of iterations; 10.000 - 99.999 -> 5 bytes + const lowerNumber = Math.pow(10, iterationsLength - 1); + const greaterNumber = Math.pow(10, iterationsLength) - 1; + const iterations = Math.floor(Math.random() * (greaterNumber - lowerNumber)) + lowerNumber; + + const encryptionKey = await pbkdf2Promise(tenSecret, salt, iterations, keyByteLength, 'sha512'); + //todo chacha20-poly1305 (clean db) + const cipher = crypto.createCipheriv('aes-256-gcm', encryptionKey, initializationVector, {authTagLength:16}); + const encryptedData = Buffer.concat([cipher.update(password, 'utf8'), cipher.final()]); + const authTag = cipher.getAuthTag(); + const predicate = iterations.toString(16); + const data = Buffer.concat([salt, initializationVector, authTag, encryptedData]).toString('hex'); + + return `${predicate}:${data}`; +}; +exports.decryptPassword = async function (ctx, password) { + const pbkdf2Promise = util.promisify(crypto.pbkdf2); + const tenSecret = ctx.getCfg('aesEncrypt.secret', cfgSecret); + const tenAESConfig = ctx.getCfg('aesEncrypt.config', cfgAESConfig) ?? {}; + const { + keyByteLength = 32, + saltByteLength = 64, + initializationVectorByteLength = 16, + } = tenAESConfig; + + const [iterations, dataHex] = password.split(':'); + const data = Buffer.from(dataHex, 'hex'); + // authTag in node.js equals 16 bytes(128 bits), see https://stackoverflow.com/questions/33976117/does-node-js-crypto-use-fixed-tag-size-with-gcm-mode + const delta = [saltByteLength, initializationVectorByteLength, 16]; + const pointerArray = []; + + for (let byte = 0, i = 0; i < delta.length; i++) { + const deltaValue = delta[i]; + pointerArray.push(data.subarray(byte, byte + deltaValue)); + byte += deltaValue; + + if (i === delta.length - 1) { + pointerArray.push(data.subarray(byte)); } } - return getSecretByElem(secretElem); + + const [ + salt, + initializationVector, + authTag, + encryptedData + ] = pointerArray; + + const decryptionKey = await pbkdf2Promise(tenSecret, salt, parseInt(iterations, 16), keyByteLength, 'sha512'); + const decipher = crypto.createDecipheriv('aes-256-gcm', decryptionKey, initializationVector, {authTagLength:16}); + decipher.setAuthTag(authTag); + + return Buffer.concat([decipher.update(encryptedData, 'binary'), decipher.final()]).toString(); +}; +exports.getDateTimeTicks = function(date) { + return BigInt(date.getTime() * 10000) + 621355968000000000n; +}; +exports.convertLicenseInfoToFileParams = function(licenseInfo) { + // todo + // { + // user_quota = 0; + // portal_count = 0; + // process = 2; + // ssbranding = false; + // whiteLabel = false; + // } + let license = {}; + license.start_date = licenseInfo.startDate && licenseInfo.startDate.toJSON(); + license.end_date = licenseInfo.endDate && licenseInfo.endDate.toJSON(); + license.timelimited = 0 !== (constants.LICENSE_MODE.Limited & licenseInfo.mode); + license.trial = 0 !== (constants.LICENSE_MODE.Trial & licenseInfo.mode); + license.developer = 0 !== (constants.LICENSE_MODE.Developer & licenseInfo.mode); + license.branding = licenseInfo.branding; + license.customization = licenseInfo.customization; + license.advanced_api = licenseInfo.advancedApi; + license.connections = licenseInfo.connections; + license.connections_view = licenseInfo.connectionsView; + license.users_count = licenseInfo.usersCount; + license.users_view_count = licenseInfo.usersViewCount; + license.users_expire = licenseInfo.usersExpire / constants.LICENSE_EXPIRE_USERS_ONE_DAY; + license.customer_id = licenseInfo.customerId; + license.alias = licenseInfo.alias; + license.multitenancy = licenseInfo.multitenancy; + return license; +}; +exports.convertLicenseInfoToServerParams = function(licenseInfo) { + let license = {}; + license.workersCount = licenseInfo.count; + license.resultType = licenseInfo.type; + license.packageType = licenseInfo.packageType; + license.buildDate = licenseInfo.buildDate && licenseInfo.buildDate.toJSON(); + license.buildVersion = commonDefines.buildVersion; + license.buildNumber = commonDefines.buildNumber; + return license; +}; +exports.checkBaseUrl = function(ctx, baseUrl, opt_storageCfg) { + let storageExternalHost = opt_storageCfg ? opt_storageCfg.externalHost : cfgStorageExternalHost + const tenStorageExternalHost = ctx.getCfg('storage.externalHost', storageExternalHost); + return tenStorageExternalHost ? tenStorageExternalHost : baseUrl; +}; +exports.resolvePath = function(object, path, defaultValue) { + return path.split('.').reduce((o, p) => o ? o[p] : defaultValue, object); +}; +Date.isLeapYear = function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); +}; + +Date.getDaysInMonth = function (year, month) { + return [31, (Date.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; +}; + +Date.prototype.isLeapYear = function () { + return Date.isLeapYear(this.getUTCFullYear()); +}; + +Date.prototype.getDaysInMonth = function () { + return Date.getDaysInMonth(this.getUTCFullYear(), this.getUTCMonth()); +}; + +Date.prototype.addMonths = function (value) { + var n = this.getUTCDate(); + this.setUTCDate(1); + this.setUTCMonth(this.getUTCMonth() + value); + this.setUTCDate(Math.min(n, this.getDaysInMonth())); + return this; +}; +function getMonthDiff(d1, d2) { + var months; + months = (d2.getUTCFullYear() - d1.getUTCFullYear()) * 12; + months -= d1.getUTCMonth(); + months += d2.getUTCMonth(); + return months; +} +exports.getMonthDiff = getMonthDiff; +exports.getLicensePeriod = function(startDate, now) { + startDate = new Date(startDate.getTime());//clone + startDate.addMonths(getMonthDiff(startDate, now)); + if (startDate > now) { + startDate.addMonths(-1); + } + startDate.setUTCHours(0,0,0,0); + return startDate.getTime(); +}; + +exports.removeIllegalCharacters = function(filename) { + return filename?.replace(/[/\\?%*:|"<>]/g, '-') || filename; +} +exports.getFunctionArguments = function(func) { + return func.toString(). + replace(/[\r\n\s]+/g, ' '). + match(/(?:function\s*\w*)?\s*(?:\((.*?)\)|([^\s]+))/). + slice(1, 3). + join(''). + split(/\s*,\s*/); +}; +exports.isUselesSfc = function(row, cmd) { + return !(row && commonDefines.FileStatus.SaveVersion === row.status && cmd.getStatusInfoIn() === row.status_info); +}; +exports.getChangesFileHeader = function() { + return `CHANGES\t${commonDefines.buildVersion}\n`; +}; +exports.checksumFile = function(hashName, path) { + //https://stackoverflow.com/a/44643479 + return new Promise((resolve, reject) => { + const hash = crypto.createHash(hashName); + const stream = fs.createReadStream(path); + stream.on('error', err => reject(err)); + stream.on('data', chunk => hash.update(chunk)); + stream.on('end', () => resolve(hash.digest('hex'))); + }); +}; + +function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item)); } -exports.getSecret = getSecret; -function fillJwtForRequest(opt_payload) { - let data = {}; - if(opt_payload){ - data.payload = opt_payload; + +function deepMergeObjects(target, ...sources) { + if (!sources.length) { + return target; } - let options = {algorithm: cfgTokenOutboxAlgorithm, expiresIn: cfgTokenOutboxExpires}; - let secret = getSecretByElem(cfgSignatureSecretOutbox); - return jwt.sign(data, secret, options); + const source = sources.shift(); + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) { + Object.assign(target, { [key]: {} }); + } + + deepMergeObjects(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return deepMergeObjects(target, ...sources); } -exports.fillJwtForRequest = fillJwtForRequest; -exports.forwarded = forwarded; +exports.isObject = isObject; +exports.deepMergeObjects = deepMergeObjects; diff --git a/DocService/npm-shrinkwrap.json b/DocService/npm-shrinkwrap.json new file mode 100644 index 0000000000..ed8b504d21 --- /dev/null +++ b/DocService/npm-shrinkwrap.json @@ -0,0 +1,3863 @@ +{ + "name": "coauthoring", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-auth": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz", + "integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.1.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-client": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.7.3.tgz", + "integrity": "sha512-kleJ1iUTxcO32Y06dH9Pfi9K4U+Tlb111WXEnbt7R/ne+NLRwppZiTGJuTD5VVoxTMK5NTbEtm5t2vcdNCFe2g==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-rest-pipeline": "^1.9.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-http-compat": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-1.3.0.tgz", + "integrity": "sha512-ZN9avruqbQ5TxopzG3ih3KRy52n8OAbitX3fnZT5go4hzu0J+KVPSzkL+Wt3hpJpdG8WIfg1sBD1tWkgUdEpBA==", + "requires": { + "@azure/abort-controller": "^1.0.4", + "@azure/core-client": "^1.3.0", + "@azure/core-rest-pipeline": "^1.3.0" + } + }, + "@azure/core-lro": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz", + "integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-paging": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.5.0.tgz", + "integrity": "sha512-zqWdVIt+2Z+3wqxEOGzR5hXFZ8MGKK52x4vFLw8n58pR6ZfKRx3EXYTxTaYxYHc/PexPUTyimcTWFJbji9Z6Iw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-rest-pipeline": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.1.tgz", + "integrity": "sha512-SsyWQ+T5MFQRX+M8H/66AlaI6HyCbQStGfFngx2fuiW+vKI2DkhtOvbYodPyf9fOe/ARLWWc3ohX54lQ5Kmaog==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.4.0", + "@azure/core-tracing": "^1.0.1", + "@azure/core-util": "^1.3.0", + "@azure/logger": "^1.0.0", + "form-data": "^4.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/core-tracing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.0.1.tgz", + "integrity": "sha512-I5CGMoLtX+pI17ZdiFJZgxMJApsK6jjfm85hpgp3oazCdq5Wxgh4wMr7ge/TTWW1B5WBuvIOI1fMU/FrOAMKrw==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/core-util": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.4.0.tgz", + "integrity": "sha512-eGAyJpm3skVQoLiRqm/xPa+SXi/NPDdSHMxbRAz2lSprd+Zs+qrpQGQQ2VQ3Nttu+nSZR4XoYQC71LbEI7jsig==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/identity": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-2.1.0.tgz", + "integrity": "sha512-BPDz1sK7Ul9t0l9YKLEa8PHqWU4iCfhGJ+ELJl6c8CP3TpJt2urNCbm0ZHsthmxRsYoMPbz2Dvzj30zXZVmAFw==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.4.0", + "@azure/core-rest-pipeline": "^1.1.0", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "@azure/msal-browser": "^2.26.0", + "@azure/msal-common": "^7.0.0", + "@azure/msal-node": "^1.10.0", + "events": "^3.0.0", + "jws": "^4.0.0", + "open": "^8.0.0", + "stoppable": "^1.1.0", + "tslib": "^2.2.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + } + } + }, + "@azure/keyvault-keys": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/@azure/keyvault-keys/-/keyvault-keys-4.7.2.tgz", + "integrity": "sha512-VdIH6PjbQ3J5ntK+xeI8eOe1WsDxF9ndXw8BPR/9MZVnIj0vQNtNCS6gpR7EFQeGcs8XjzMfHm0AvKGErobqJQ==", + "requires": { + "@azure/abort-controller": "^1.0.0", + "@azure/core-auth": "^1.3.0", + "@azure/core-client": "^1.5.0", + "@azure/core-http-compat": "^1.3.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.1.1", + "@azure/core-rest-pipeline": "^1.8.1", + "@azure/core-tracing": "^1.0.0", + "@azure/core-util": "^1.0.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.2.0" + } + }, + "@azure/logger": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.0.4.tgz", + "integrity": "sha512-ustrPY8MryhloQj7OWGe+HrYx+aoiOxzbXTtgblbV3xwCqpzUK36phH3XNHQKj3EPonyFUuDTfR3qFhTEAuZEg==", + "requires": { + "tslib": "^2.2.0" + } + }, + "@azure/msal-browser": { + "version": "2.38.2", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-2.38.2.tgz", + "integrity": "sha512-71BeIn2we6LIgMplwCSaMq5zAwmalyJR3jFcVOZxNVfQ1saBRwOD+P77nLs5vrRCedVKTq8RMFhIOdpMLNno0A==", + "requires": { + "@azure/msal-common": "13.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.0.tgz", + "integrity": "sha512-/VFWTicjcJbrGp3yQP7A24xU95NiDMe23vxIU1U6qdRPFsprMDNUohMudclnd+WSHE4/McqkZs/nUU3sAKkVjg==" + } + } + }, + "@azure/msal-common": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-7.6.0.tgz", + "integrity": "sha512-XqfbglUTVLdkHQ8F9UQJtKseRr3sSnr9ysboxtoswvaMVaEfvyLtMoHv9XdKUfOc0qKGzNgRFd9yRjIWVepl6Q==" + }, + "@azure/msal-node": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.18.3.tgz", + "integrity": "sha512-lI1OsxNbS/gxRD4548Wyj22Dk8kS7eGMwD9GlBZvQmFV8FJUXoXySL1BiNzDsHUE96/DS/DHmA+F73p1Dkcktg==", + "requires": { + "@azure/msal-common": "13.3.0", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "dependencies": { + "@azure/msal-common": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-13.3.0.tgz", + "integrity": "sha512-/VFWTicjcJbrGp3yQP7A24xU95NiDMe23vxIU1U6qdRPFsprMDNUohMudclnd+WSHE4/McqkZs/nUU3sAKkVjg==" + } + } + }, + "@ioredis/commands": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", + "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" + }, + "@jimp/bmp": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.10.tgz", + "integrity": "sha512-1UXRl1Nw1KptZ1r0ANqtXOst9vGH51dq7keVKQzyyTO2lz4dOaezS9StuSTNh+RmiHg/SVPaFRpPfB0S/ln4Kg==", + "requires": { + "@jimp/utils": "^0.22.10", + "bmp-js": "^0.1.0" + } + }, + "@jimp/core": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.10.tgz", + "integrity": "sha512-ZKyrehVy6wu1PnBXIUpn/fXmyMRQiVSbvHDubgXz4bfTOao3GiOurKHjByutQIgozuAN6ZHWiSge1dKA+dex3w==", + "requires": { + "@jimp/utils": "^0.22.10", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, + "@jimp/custom": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.10.tgz", + "integrity": "sha512-sPZkUYe1hu0iIgNisjizxPJqq2vaaKvkCkPoXq2U6UV3ZA1si/WVdrg25da3IcGIEV+83AoHgM8TvqlLgrCJsg==", + "requires": { + "@jimp/core": "^0.22.10" + } + }, + "@jimp/gif": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.10.tgz", + "integrity": "sha512-yEX2dSpamvkSx1PPDWGnKeWDrBz0vrCKjVG/cn4Zr68MRRT75tbZIeOrBa+RiUpY3ho5ix7d36LkYvt3qfUIhQ==", + "requires": { + "@jimp/utils": "^0.22.10", + "gifwrap": "^0.10.1", + "omggif": "^1.0.9" + } + }, + "@jimp/jpeg": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.10.tgz", + "integrity": "sha512-6bu98pAcVN4DY2oiDLC4TOgieX/lZrLd1tombWZOFCN5PBmqaHQxm7IUmT+Wj4faEvh8QSHgVLSA+2JQQRJWVA==", + "requires": { + "@jimp/utils": "^0.22.10", + "jpeg-js": "^0.4.4" + } + }, + "@jimp/plugin-blit": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz", + "integrity": "sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-blur": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz", + "integrity": "sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-circle": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz", + "integrity": "sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-color": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.12.tgz", + "integrity": "sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA==", + "requires": { + "@jimp/utils": "^0.22.12", + "tinycolor2": "^1.6.0" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-contain": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz", + "integrity": "sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-cover": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz", + "integrity": "sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-crop": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz", + "integrity": "sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-displace": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz", + "integrity": "sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-dither": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz", + "integrity": "sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-fisheye": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz", + "integrity": "sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-flip": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz", + "integrity": "sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-gaussian": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz", + "integrity": "sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-invert": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz", + "integrity": "sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-mask": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz", + "integrity": "sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-normalize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz", + "integrity": "sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-print": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.12.tgz", + "integrity": "sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ==", + "requires": { + "@jimp/utils": "^0.22.12", + "load-bmfont": "^1.4.1" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-resize": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz", + "integrity": "sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-rotate": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz", + "integrity": "sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-scale": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz", + "integrity": "sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-shadow": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz", + "integrity": "sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugin-threshold": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz", + "integrity": "sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw==", + "requires": { + "@jimp/utils": "^0.22.12" + }, + "dependencies": { + "@jimp/utils": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.12.tgz", + "integrity": "sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + } + } + }, + "@jimp/plugins": { + "version": "0.22.12", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.12.tgz", + "integrity": "sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww==", + "requires": { + "@jimp/plugin-blit": "^0.22.12", + "@jimp/plugin-blur": "^0.22.12", + "@jimp/plugin-circle": "^0.22.12", + "@jimp/plugin-color": "^0.22.12", + "@jimp/plugin-contain": "^0.22.12", + "@jimp/plugin-cover": "^0.22.12", + "@jimp/plugin-crop": "^0.22.12", + "@jimp/plugin-displace": "^0.22.12", + "@jimp/plugin-dither": "^0.22.12", + "@jimp/plugin-fisheye": "^0.22.12", + "@jimp/plugin-flip": "^0.22.12", + "@jimp/plugin-gaussian": "^0.22.12", + "@jimp/plugin-invert": "^0.22.12", + "@jimp/plugin-mask": "^0.22.12", + "@jimp/plugin-normalize": "^0.22.12", + "@jimp/plugin-print": "^0.22.12", + "@jimp/plugin-resize": "^0.22.12", + "@jimp/plugin-rotate": "^0.22.12", + "@jimp/plugin-scale": "^0.22.12", + "@jimp/plugin-shadow": "^0.22.12", + "@jimp/plugin-threshold": "^0.22.12", + "timm": "^1.6.1" + } + }, + "@jimp/png": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.10.tgz", + "integrity": "sha512-RYinU7tZToeeR2g2qAMn42AU+8OUHjXPKZZ9RkmoL4bguA1xyZWaSdr22/FBkmnHhOERRlr02KPDN1OTOYHLDQ==", + "requires": { + "@jimp/utils": "^0.22.10", + "pngjs": "^6.0.0" + }, + "dependencies": { + "pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==" + } + } + }, + "@jimp/tiff": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.10.tgz", + "integrity": "sha512-OaivlSYzpNTHyH/h7pEtl3A7F7TbsgytZs52GLX/xITW92ffgDgT6PkldIrMrET6ERh/hdijNQiew7IoEEr2og==", + "requires": { + "utif2": "^4.0.1" + } + }, + "@jimp/types": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.10.tgz", + "integrity": "sha512-u/r+XYzbCx4zZukDmxx8S0er3Yq3iDPI6+31WKX0N18i2qPPJYcn8qwIFurfupRumGvJ8SlGLCgt/T+Y8zzUIw==", + "requires": { + "@jimp/bmp": "^0.22.10", + "@jimp/gif": "^0.22.10", + "@jimp/jpeg": "^0.22.10", + "@jimp/png": "^0.22.10", + "@jimp/tiff": "^0.22.10", + "timm": "^1.6.1" + } + }, + "@jimp/utils": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.10.tgz", + "integrity": "sha512-ztlOK9Mm2iLG2AMoabzM4i3WZ/FtshcgsJCbZCRUs/DKoeS2tySRJTnQZ1b7Roq0M4Ce+FUAxnCAcBV0q7PH9w==", + "requires": { + "regenerator-runtime": "^0.13.3" + } + }, + "@js-joda/core": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/@js-joda/core/-/core-5.5.3.tgz", + "integrity": "sha512-7dqNYwG8gCt4hfg5PKgM7xLEcgSBcx/UgC92OMnhMmvAnq11QzDFPrxUkNR/u5kn17WWLZ8beZ4A3Qrz4pZcmQ==" + }, + "@napi-rs/snappy-android-arm-eabi": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm-eabi/-/snappy-android-arm-eabi-7.2.2.tgz", + "integrity": "sha512-H7DuVkPCK5BlAr1NfSU8bDEN7gYs+R78pSHhDng83QxRnCLmVIZk33ymmIwurmoA1HrdTxbkbuNl+lMvNqnytw==", + "optional": true + }, + "@napi-rs/snappy-android-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-android-arm64/-/snappy-android-arm64-7.2.2.tgz", + "integrity": "sha512-2R/A3qok+nGtpVK8oUMcrIi5OMDckGYNoBLFyli3zp8w6IArPRfg1yOfVUcHvpUDTo9T7LOS1fXgMOoC796eQw==", + "optional": true + }, + "@napi-rs/snappy-darwin-arm64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-arm64/-/snappy-darwin-arm64-7.2.2.tgz", + "integrity": "sha512-USgArHbfrmdbuq33bD5ssbkPIoT7YCXCRLmZpDS6dMDrx+iM7eD2BecNbOOo7/v1eu6TRmQ0xOzeQ6I/9FIi5g==", + "optional": true + }, + "@napi-rs/snappy-darwin-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-darwin-x64/-/snappy-darwin-x64-7.2.2.tgz", + "integrity": "sha512-0APDu8iO5iT0IJKblk2lH0VpWSl9zOZndZKnBYIc+ei1npw2L5QvuErFOTeTdHBtzvUHASB+9bvgaWnQo4PvTQ==", + "optional": true + }, + "@napi-rs/snappy-freebsd-x64": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-freebsd-x64/-/snappy-freebsd-x64-7.2.2.tgz", + "integrity": "sha512-mRTCJsuzy0o/B0Hnp9CwNB5V6cOJ4wedDTWEthsdKHSsQlO7WU9W1yP7H3Qv3Ccp/ZfMyrmG98Ad7u7lG58WXA==", + "optional": true + }, + "@napi-rs/snappy-linux-arm-gnueabihf": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm-gnueabihf/-/snappy-linux-arm-gnueabihf-7.2.2.tgz", + "integrity": "sha512-v1uzm8+6uYjasBPcFkv90VLZ+WhLzr/tnfkZ/iD9mHYiULqkqpRuC8zvc3FZaJy5wLQE9zTDkTJN1IvUcZ+Vcg==", + "optional": true + }, + "@napi-rs/snappy-linux-arm64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-gnu/-/snappy-linux-arm64-gnu-7.2.2.tgz", + "integrity": "sha512-LrEMa5pBScs4GXWOn6ZYXfQ72IzoolZw5txqUHVGs8eK4g1HR9HTHhb2oY5ySNaKakG5sOgMsb1rwaEnjhChmQ==", + "optional": true + }, + "@napi-rs/snappy-linux-arm64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-arm64-musl/-/snappy-linux-arm64-musl-7.2.2.tgz", + "integrity": "sha512-3orWZo9hUpGQcB+3aTLW7UFDqNCQfbr0+MvV67x8nMNYj5eAeUtMmUE/HxLznHO4eZ1qSqiTwLbVx05/Socdlw==", + "optional": true + }, + "@napi-rs/snappy-linux-x64-gnu": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-gnu/-/snappy-linux-x64-gnu-7.2.2.tgz", + "integrity": "sha512-jZt8Jit/HHDcavt80zxEkDpH+R1Ic0ssiVCoueASzMXa7vwPJeF4ZxZyqUw4qeSy7n8UUExomu8G8ZbP6VKhgw==", + "optional": true + }, + "@napi-rs/snappy-linux-x64-musl": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-linux-x64-musl/-/snappy-linux-x64-musl-7.2.2.tgz", + "integrity": "sha512-Dh96IXgcZrV39a+Tej/owcd9vr5ihiZ3KRix11rr1v0MWtVb61+H1GXXlz6+Zcx9y8jM1NmOuiIuJwkV4vZ4WA==", + "optional": true + }, + "@napi-rs/snappy-win32-arm64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-arm64-msvc/-/snappy-win32-arm64-msvc-7.2.2.tgz", + "integrity": "sha512-9No0b3xGbHSWv2wtLEn3MO76Yopn1U2TdemZpCaEgOGccz1V+a/1d16Piz3ofSmnA13HGFz3h9NwZH9EOaIgYA==", + "optional": true + }, + "@napi-rs/snappy-win32-ia32-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-ia32-msvc/-/snappy-win32-ia32-msvc-7.2.2.tgz", + "integrity": "sha512-QiGe+0G86J74Qz1JcHtBwM3OYdTni1hX1PFyLRo3HhQUSpmi13Bzc1En7APn+6Pvo7gkrcy81dObGLDSxFAkQQ==", + "optional": true + }, + "@napi-rs/snappy-win32-x64-msvc": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/snappy-win32-x64-msvc/-/snappy-win32-x64-msvc-7.2.2.tgz", + "integrity": "sha512-a43cyx1nK0daw6BZxVcvDEXxKMFLSBSDTAhsFD0VqSKcC7MGUBMaqyoWUcMiI7LBSz4bxUmxDWKfCYzpEmeb3w==", + "optional": true + }, + "@oozcitak/dom": { + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz", + "integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "requires": { + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==" + }, + "@redis/bloom": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz", + "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==" + }, + "@redis/client": { + "version": "1.5.12", + "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.12.tgz", + "integrity": "sha512-/ZjE18HRzMd80eXIIUIPcH81UoZpwulbo8FmbElrjPqH0QC0SeIKu1BOU49bO5trM5g895kAjhvalt5h77q+4A==", + "requires": { + "cluster-key-slot": "1.1.2", + "generic-pool": "3.9.0", + "yallist": "4.0.0" + } + }, + "@redis/graph": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz", + "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==" + }, + "@redis/json": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.6.tgz", + "integrity": "sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==" + }, + "@redis/search": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.6.tgz", + "integrity": "sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==" + }, + "@redis/time-series": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.5.tgz", + "integrity": "sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==" + }, + "@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "@tediousjs/connection-string": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@tediousjs/connection-string/-/connection-string-0.4.4.tgz", + "integrity": "sha512-Qssn7gmOILmqD0zkfA09YyFd52UajWYkLTTSi4Dx/XZaUuVcx4W4guv2rAVc5mm8wYRdonmG/HfFH3PS6izXAg==" + }, + "@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==" + }, + "@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "18.7.16", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.16.tgz", + "integrity": "sha512-EQHhixfu+mkqHMZl1R2Ovuvn47PUw18azMJOTwSZr9/fhzHNGXAJ0ma0dayRVchprpCj0Kc1K1xKoWaATWF1qg==" + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "dependencies": { + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + } + } + }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "ajv": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.9.0.tgz", + "integrity": "sha512-qOKJyNj/h+OWx7s5DePL6Zu1KeM9jPZhwBqs+7DzP6bGOvqzVCSf0xueYmVuaC/oQ/VtS2zLMLHdQFbkka+XDQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "apicache": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/apicache/-/apicache-1.6.3.tgz", + "integrity": "sha512-jS3VfUFpQ9BesFQZcdd1vVYg3ZsO2kGPmTJHqycIYPAQs54r74CRiyj8DuzJpwzLwIfCBYzh4dy9Jt8xYbo27w==" + }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, + "async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base64-stream/-/base64-stream-1.0.0.tgz", + "integrity": "sha512-BQQZftaO48FcE1Kof9CmXMFaAdqkcNorgc8CxesZv9nMbbTF1EFyQe89UOuh//QMmdtfUDXyO8rgUalemL5ODA==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "requires": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" + }, + "body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, + "bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==" + }, + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "requires": { + "balanced-match": "^1.0.0" + } + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==" + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "centra": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/centra/-/centra-2.7.0.tgz", + "integrity": "sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg==", + "requires": { + "follow-redirects": "^1.15.6" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cluster-key-slot": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz", + "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/config/-/config-2.0.1.tgz", + "integrity": "sha512-aTaviJnC8ZjQYx8kQf4u6tWqIxWolyQQ3LqXgnCLAsIb78JrUshHG0YuzIarzTaVVe1Pazms3TXImfYra8UsyQ==", + "requires": { + "json5": "^1.0.1" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "cron": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.5.0.tgz", + "integrity": "sha512-j7zMFLrcSta53xqOvETUt8ge+PM14GtF47gEGJJeVlM6qP24/eWHSgtiWiEiKBR2sHS8xZaBQZq4D7vFXg8dcQ==", + "requires": { + "moment-timezone": "^0.5.x" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "define-data-property": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", + "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, + "define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, + "dmdb": { + "version": "1.0.14280", + "resolved": "https://registry.npmjs.org/dmdb/-/dmdb-1.0.14280.tgz", + "integrity": "sha512-vG6sA4s4Q3kLfO4RDTuA7b+//wNC/5ZEZk3MRHUtCqCcyMWoRaSZ8FWX60FFudaCoIi05kYGf8MlsMsy4HB6JA==", + "requires": { + "iconv-lite": "^0.5.1", + "snappy": "^7.2.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", + "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, + "dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, + "double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, + "ecdsa-sig-formatter": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.10.tgz", + "integrity": "sha1-HFlQAPBKiJffuFAAiSoPTDOvhsM=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "requires": { + "jake": "^10.8.5" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "engine.io": { + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.3.tgz", + "integrity": "sha512-2hkLItQMBkoYSagneiisupWGvsQlWXqzhSMvsjaM8GYbnfUsX7tzYQq9QARnate5LRedVTX+MbkSZAANAr3NtQ==", + "requires": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~1.0.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1" + }, + "dependencies": { + "cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==" + }, + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==" + }, + "es-abstract": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", + "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.11" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, + "es-aggregate-error": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/es-aggregate-error/-/es-aggregate-error-1.0.11.tgz", + "integrity": "sha512-DCiZiNlMlbvofET/cE55My387NiLvuGToBEZDdK9U2G3svDCjL8WOgO5Il6lO83nQ8qmag/R9nArdpaFQ/m3lA==", + "requires": { + "define-data-property": "^1.1.0", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.2.1", + "globalthis": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "set-function-name": "^2.0.1" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, + "express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + } + } + }, + "fakeredis": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fakeredis/-/fakeredis-2.0.0.tgz", + "integrity": "sha1-dS+4MoGazjG+iGaM8TWNbxQ4r8A=", + "requires": { + "redis": "2.6.0-0" + }, + "dependencies": { + "redis": { + "version": "2.6.0-0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.6.0-0.tgz", + "integrity": "sha1-c/Z03T5Uo6dnA9HtJga05gC+B20=", + "requires": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.1.0", + "redis-parser": "^1.2.0" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, + "file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "requires": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } + } + }, + "follow-redirects": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.8.tgz", + "integrity": "sha512-xgrmBhBToVKay1q2Tao5LI26B83UhrB/vM1avwVSDzt8rx3rO6AizBAaF46EgksTVr+rFTQaqZZ9MVBfUe4nig==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, + "generic-pool": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz", + "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==" + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "gifwrap": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.10.1.tgz", + "integrity": "sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw==", + "requires": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "requires": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + } + } + }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "requires": { + "@types/node": "16.9.1" + }, + "dependencies": { + "@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + } + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "ioredis": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.1.tgz", + "integrity": "sha512-C+IBcMysM6v52pTLItYMeV4Hz7uriGtoJdz7SSBDX6u+zwSYGirLdQh3L7t/OItWITcw3gTFMjJReYUwS4zihg==", + "requires": { + "@ioredis/commands": "^1.1.1", + "cluster-key-slot": "^1.1.0", + "debug": "^4.3.4", + "denque": "^2.1.0", + "lodash.defaults": "^4.2.0", + "lodash.isarguments": "^3.1.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0", + "standard-as-callback": "^2.1.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "requires": { + "redis-errors": "^1.0.0" + } + } + } + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + }, + "is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "requires": { + "which-typed-array": "^1.1.11" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "requires": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, + "jake": { + "version": "10.8.7", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz", + "integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==", + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + } + }, + "jimp": { + "version": "0.22.10", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.10.tgz", + "integrity": "sha512-lCaHIJAgTOsplyJzC1w/laxSxrbSsEBw4byKwXgUdMmh+ayPsnidTblenQm+IvhIs44Gcuvlb6pd2LQ0wcKaKg==", + "requires": { + "@jimp/custom": "^0.22.10", + "@jimp/plugins": "^0.22.10", + "@jimp/types": "^0.22.10", + "regenerator-runtime": "^0.13.3" + } + }, + "jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" + }, + "js-md4": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", + "integrity": "sha512-/GDnfQYsltsjRswQhN9fhv3EMw2sCpUdrdxyWDOUK7eyD++r3gRhzgiQgc/x4MAv2i1iuQ4lxO5mvqM3vj4bwA==" + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbi": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jsbi/-/jsbi-4.3.0.tgz", + "integrity": "sha512-SnZNcinB4RIcnEyZqFPdGPVgrg2AcnykiBy0sHVJQKHYeaLUvi3Exj+iaPpLnFVkDPZIV4U0yvgC9/R4uEAZ9g==" + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "jsonwebtoken": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", + "requires": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "dependencies": { + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "jwa": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.1.6.tgz", + "integrity": "sha512-tBO/cf++BUsJkYql/kBbJroKOgHWEigTKBAjjBEmrMGYd1QMBC74Hr4Wo2zCZw6ZrVhlJPvoMrkcOnlWR/DJfw==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.10", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + }, + "dependencies": { + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + } + } + }, + "load-bmfont": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.2.tgz", + "integrity": "sha512-qElWkmjW9Oq1F9EI5Gt7aD9zcdHb9spJCW1L/dmPf7KzCCEJxq8nhHz5eCgI9aMf7vrG/wyaCqdsI+Iy9ZTlog==", + "requires": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^3.7.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + } + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + }, + "lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==" + }, + "long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", + "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==" + }, + "mime-db": { + "version": "1.49.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz", + "integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + } + } + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + }, + "dependencies": { + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + } + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" + }, + "moment-timezone": { + "version": "0.5.43", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", + "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "requires": { + "moment": "^2.29.4" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "mssql": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/mssql/-/mssql-9.1.1.tgz", + "integrity": "sha512-m0yTx9xzUtTvJpWJHqknUXUDPRnJXZYOOFNygnNIXn1PBkLsC/rkXQdquObd+M0ZPlBhGC00Jg28zG0wCl7VWg==", + "requires": { + "@tediousjs/connection-string": "^0.4.1", + "commander": "^9.4.0", + "debug": "^4.3.3", + "rfdc": "^1.3.0", + "tarn": "^3.0.2", + "tedious": "^15.0.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "multer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.3.tgz", + "integrity": "sha512-np0YLKncuZoTzufbkM6wEKp68EhWJXcU6fq6QqrSwkckd2LlMgd1UqhUJLj6NS/5sZ8dE8LYDWslsltJznnXlg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, + "multi-integer-range": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/multi-integer-range/-/multi-integer-range-4.0.7.tgz", + "integrity": "sha512-THuAnyKxIogrhWvHXHMEWxR90TCFcz/RHsnTcGKAOxdOMaNZSKkujOHMC2LZGMc/j+Jdf31FJcL66au/U0mS1Q==" + }, + "multiparty": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.1.tgz", + "integrity": "sha512-AvESCnNoQlZiOfP9R4mxN8M9csy2L16EIbWIkt3l4FuGti9kXBS8QVzlfyg4HEnarJhrzZilgNFlZtqmoiAIIA==", + "requires": { + "fd-slicer": "1.1.0", + "http-errors": "~1.7.0", + "safe-buffer": "5.1.2", + "uid-safe": "2.1.5" + }, + "dependencies": { + "http-errors": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz", + "integrity": "sha512-jWEUgtZWGSMba9I1N3gc1HmvpBUaNC9vDdA46yScAdp+C5rdEuKWUBLWTQpW9FwSWSbYYs++b6SDCxf9UEJzfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + } + } + }, + "mysql2": { + "version": "3.9.8", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.9.8.tgz", + "integrity": "sha512-+5JKNjPuks1FNMoy9TYpl77f+5frbTklz7eb3XDwbpsERRLEeXiW2PDEkakYF50UuKU2qwfGnyXpKYvukv8mGA==", + "requires": { + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.6.3", + "long": "^5.2.1", + "lru-cache": "^8.0.0", + "named-placeholders": "^1.1.3", + "seq-queue": "^0.0.5", + "sqlstring": "^2.3.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==" + } + } + }, + "named-placeholders": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.3.tgz", + "integrity": "sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w==", + "requires": { + "lru-cache": "^7.14.1" + }, + "dependencies": { + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==" + } + } + }, + "native-duplexpair": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/native-duplexpair/-/native-duplexpair-1.0.0.tgz", + "integrity": "sha512-E7QQoM+3jvNtlmyfqRZ0/U75VFgCls+fSkbml2MpgWkWyz3ox8Y58gNhfuziuQYGNNQAbFZJQck55LHCnCK6CA==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==" + }, + "node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "requires": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + } + }, + "oracledb": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.3.0.tgz", + "integrity": "sha512-fr3U66QxgGXb5cs/ozLBQU50TMbZcBQEWvSaj2rJAXG8KRrsZcGOK8JTlZL1yJHeW8cSjOm6n/wTw3SJksGjDg==" + }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "parse-bmfont-xml": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.6.tgz", + "integrity": "sha512-0cEliVMZEhrFDwMh4SxIyVJpqYoOWDJ9P895tFuS+XuNzI5UBmBk5U5O4KuJdTnZpSBI4LFA2+ZiJaiwfSwlMA==", + "requires": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.5.0" + } + }, + "parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==" + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "pg": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.11.3.tgz", + "integrity": "sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-cloudflare": "^1.1.1", + "pg-connection-string": "^2.6.2", + "pg-pool": "^3.6.1", + "pg-protocol": "^1.6.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-cloudflare": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz", + "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==", + "optional": true + }, + "pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.6.1.tgz", + "integrity": "sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==" + }, + "pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "requires": { + "split2": "^4.1.0" + } + }, + "phin": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/phin/-/phin-3.7.1.tgz", + "integrity": "sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ==", + "requires": { + "centra": "^2.7.0" + } + }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "requires": { + "pngjs": "^3.0.0" + } + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "dependencies": { + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + } + } + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "requires": { + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "redis": { + "version": "4.6.11", + "resolved": "https://registry.npmjs.org/redis/-/redis-4.6.11.tgz", + "integrity": "sha512-kg1Lt4NZLYkAjPOj/WcyIGWfZfnyfKo1Wg9YKVSlzhFwxpFIl3LYI8BWy1Ab963LLDsTz2+OwdsesHKljB3WMQ==", + "requires": { + "@redis/bloom": "1.2.0", + "@redis/client": "1.5.12", + "@redis/graph": "1.1.1", + "@redis/json": "1.0.6", + "@redis/search": "1.1.6", + "@redis/time-series": "1.0.5" + } + }, + "redis-commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz", + "integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-1.3.0.tgz", + "integrity": "sha1-gG6+e7+3005NfB6e8oLvz60EEmo=" + }, + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + } + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" + }, + "rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==" + }, + "safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==" + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + } + } + }, + "seq-queue": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", + "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==" + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "requires": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + } + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "snappy": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/snappy/-/snappy-7.2.2.tgz", + "integrity": "sha512-iADMq1kY0v3vJmGTuKcFWSXt15qYUz7wFkArOrsSg0IFfI3nJqIJvK2/ZbEIndg7erIJLtAVX2nSOqPz7DcwbA==", + "requires": { + "@napi-rs/snappy-android-arm-eabi": "7.2.2", + "@napi-rs/snappy-android-arm64": "7.2.2", + "@napi-rs/snappy-darwin-arm64": "7.2.2", + "@napi-rs/snappy-darwin-x64": "7.2.2", + "@napi-rs/snappy-freebsd-x64": "7.2.2", + "@napi-rs/snappy-linux-arm-gnueabihf": "7.2.2", + "@napi-rs/snappy-linux-arm64-gnu": "7.2.2", + "@napi-rs/snappy-linux-arm64-musl": "7.2.2", + "@napi-rs/snappy-linux-x64-gnu": "7.2.2", + "@napi-rs/snappy-linux-x64-musl": "7.2.2", + "@napi-rs/snappy-win32-arm64-msvc": "7.2.2", + "@napi-rs/snappy-win32-ia32-msvc": "7.2.2", + "@napi-rs/snappy-win32-x64-msvc": "7.2.2" + } + }, + "socket.io": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz", + "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "requires": { + "debug": "~4.3.4", + "ws": "~8.17.1" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "requires": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "sqlstring": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", + "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==" + }, + "standard-as-callback": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", + "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" + }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, + "string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "requires": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==" + }, + "tedious": { + "version": "15.1.3", + "resolved": "https://registry.npmjs.org/tedious/-/tedious-15.1.3.tgz", + "integrity": "sha512-166EpRm5qknwhEisjZqz/mF7k14fXKJYHRg6XiAXVovd/YkyHJ3SG4Ppy89caPaNFfRr7PVYe+s4dAvKaCMFvw==", + "requires": { + "@azure/identity": "^2.0.4", + "@azure/keyvault-keys": "^4.4.0", + "@js-joda/core": "^5.2.0", + "bl": "^5.0.0", + "es-aggregate-error": "^1.0.8", + "iconv-lite": "^0.6.3", + "js-md4": "^0.3.2", + "jsbi": "^4.3.0", + "native-duplexpair": "^1.0.0", + "node-abort-controller": "^3.0.1", + "punycode": "^2.1.0", + "sprintf-js": "^1.1.2" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + } + } + }, + "timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" + }, + "tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "requires": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + } + }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + } + } + }, + "typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "underscore": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz", + "integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "utf7": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utf7/-/utf7-1.0.2.tgz", + "integrity": "sha1-lV9JCq5lO6IguUVqCod2wZk2CZE=", + "requires": { + "semver": "~5.3.0" + } + }, + "utif2": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.1.0.tgz", + "integrity": "sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w==", + "requires": { + "pako": "^1.0.11" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "whatwg-fetch": { + "version": "3.6.19", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.19.tgz", + "integrity": "sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "windows-locale": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/windows-locale/-/windows-locale-1.0.1.tgz", + "integrity": "sha512-X8B22Cg9njwV4h3C5j28xmZ2eWaO69j63WhReeglB69LOT3LoqSO4Vb6TTVSfFikh4KQ9qBOJb6+WvR4tVLTfQ==" + }, + "ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==" + }, + "xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "requires": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xmlbuilder2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.2.tgz", + "integrity": "sha512-h4MUawGY21CTdhV4xm3DG9dgsqyhDkZvVJBx88beqX8wJs3VgyGQgAn5VreHuae6unTQxh115aMK5InCVmOIKw==", + "requires": { + "@oozcitak/dom": "1.15.10", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "*", + "js-yaml": "3.14.0" + } + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/DocService/package.json b/DocService/package.json index 08acfed1ff..412126ef05 100644 --- a/DocService/package.json +++ b/DocService/package.json @@ -1,26 +1,58 @@ { "name": "coauthoring", "version": "1.0.1", - "homepage": "http://www.onlyoffice.com", + "homepage": "https://www.onlyoffice.com", "private": true, + "bin": { + "docservice": "sources/server.js", + "gc": "sources/gc.js", + "prepare4shutdown": "sources/shutdown.js" + }, "dependencies": { - "base64-stream": "^0.1.3", - "body-parser": "^1.15.2", - "co": "^4.6.0", - "config": "^1.21.0", - "cron": "^1.1.0", - "express": "^4.14.0", - "fakeredis": "^1.0.3", - "jsonwebtoken": "^7.1.9", - "jwa": "^1.1.4", - "mime": "^1.3.4", - "ms": "^0.7.2", - "multiparty": "^4.1.2", - "mysql": "^2.11.1", - "pg": "^6.1.0", - "pg-escape": "^0.2.0", - "redis": "^2.6.2", - "sockjs": "http://d2ettrnqo7v976.cloudfront.net/npm/sockjs-node/v0.3.15.14.tar.gz", - "underscore": "^1.8.3" + "ajv": "8.9.0", + "apicache": "1.6.3", + "base64-stream": "1.0.0", + "body-parser": "1.20.1", + "bottleneck": "2.19.5", + "bytes": "3.0.0", + "co": "4.6.0", + "config": "2.0.1", + "cron": "1.5.0", + "deep-equal": "1.0.1", + "dmdb": "1.0.14280", + "ejs": "3.1.10", + "exif-parser": "0.1.12", + "express": "4.19.2", + "fakeredis": "2.0.0", + "ioredis": "5.3.1", + "jimp": "0.22.10", + "jsonwebtoken": "9.0.0", + "jwa": "1.1.6", + "mime": "2.3.1", + "mime-db": "1.49.0", + "ms": "2.1.1", + "mssql": "9.1.1", + "multer": "1.4.3", + "multi-integer-range": "4.0.7", + "multiparty": "4.2.1", + "mysql2": "3.9.8", + "oracledb": "6.3.0", + "pg": "8.11.3", + "redis": "4.6.11", + "retry": "0.12.0", + "socket.io": "4.8.1", + "underscore": "1.13.1", + "utf7": "1.0.2", + "windows-locale": "1.0.1", + "xmlbuilder2": "3.0.2" + }, + "pkg": { + "scripts": [ + "./sources/editorDataMemory.js", + "./sources/editorDataRedis.js", + "./sources/pubsubRabbitMQ.js", + "../Common/sources/storage-fs.js", + "../Common/sources/storage-s3.js" + ] } } diff --git a/DocService/sources/DocsCoServer.js b/DocService/sources/DocsCoServer.js index 705a283cf0..4ca471021a 100644 --- a/DocService/sources/DocsCoServer.js +++ b/DocService/sources/DocsCoServer.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -31,150 +31,175 @@ */ /* - ----------------------------------------------------view-режим--------------------------------------------------------- - * 1) Ð”Ð»Ñ view-режима обновлÑем Ñтраницу (без быÑтрого перехода), чтобы пользователь не ÑчиталÑÑ Ð·Ð° редактируемого и не - * держал документ Ð´Ð»Ñ Ñборки (еÑли не ждать, то непонÑтен быÑтрый переход из view в edit, когда документ уже ÑобралÑÑ) - * 2) ЕÑли пользователь во view-режиме, то он не учаÑтвует в редактировании (только в chat-е). При открытии он получает - * вÑе актуальные Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² документе на момент открытиÑ. Ð”Ð»Ñ view-режима не принимаем Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸ не отправлÑем их - * view-пользователÑм (Ñ‚.к. непонÑтно что делать в Ñитуации, когда 1-пользователь наделал изменений, - * Ñохранил и Ñделал undo). - *----------------------------------------------------------------------------------------------------------------------- - *------------------------------------------------Схема ÑохранениÑ------------------------------------------------------- - * а) Один пользователь - первый раз приходÑÑ‚ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð±ÐµÐ· индекÑа, затем Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸Ñ…Ð¾Ð´ÑÑ‚ Ñ Ð¸Ð½Ð´ÐµÐºÑом, можно делать - * undo-redo (иÑÑ‚Ð¾Ñ€Ð¸Ñ Ð½Ðµ третÑÑ). ЕÑли автоÑохранение включено, то оно на любое дейÑтвие (не чаще 5-ти Ñекунд). - * b) Как только заходит второй пользователь, начинаетÑÑ ÑовмеÑтное редактирование. Ðа документ ÑтавитÑÑ lock, чтобы - * первый пользователь уÑпел Ñохранить документ (либо приÑлать unlock) - * c) Когда пользователей 2 или больше, каждое Ñохранение трет иÑторию и приÑылаетÑÑ Ñ†ÐµÐ»Ð¸ÐºÐ¾Ð¼ (без индекÑа). ЕÑли - * автоÑохранение включено, то ÑохранÑетÑÑ Ð½Ðµ чаще раз в 10-минут. - * d) Когда пользователь оÑтаетÑÑ Ð¾Ð´Ð¸Ð½, поÑле принÑÑ‚Ð¸Ñ Ñ‡ÑƒÐ¶Ð¸Ñ… изменений начинаетÑÑ Ð¿ÑƒÐ½ÐºÑ‚ 'а' - *----------------------------------------------------------------------------------------------------------------------- - *--------------------------------------------Схема работы Ñ Ñервером---------------------------------------------------- - * а) Когда вÑе уходÑÑ‚, ÑпуÑÑ‚Ñ Ð²Ñ€ÐµÐ¼Ñ cfgAscSaveTimeOutDelay на Ñервер документов шлетÑÑ ÐºÐ¾Ð¼Ð°Ð½Ð´Ð° на Ñборку. - * b) ЕÑли приходит ÑÑ‚Ð°Ñ‚ÑƒÑ '1' на CommandService.ashx, то удалоÑÑŒ Ñохранить и поднÑть верÑию. Очищаем callback-и и - * Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¸Ð· базы и из памÑти. - * Ñ) ЕÑли приходит ÑтатуÑ, отличный от '1'(Ñюда можно отнеÑти как генерацию файла, так и работа внешнего подпиÑчика - * Ñ Ð³Ð¾Ñ‚Ð¾Ð²Ñ‹Ð¼ результатом), то трем callback-и, а Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¾ÑтавлÑем. Т.к. можно будет зайти в Ñтарую - * верÑию и получить неÑобранные изменениÑ. Также ÑбраÑываем ÑÑ‚Ð°Ñ‚ÑƒÑ Ñƒ файла на неÑобранный, чтобы его можно было - * открывать без ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± ошибке верÑии. - *----------------------------------------------------------------------------------------------------------------------- - *------------------------------------------------Старт Ñервера---------------------------------------------------------- - * 1) Загружаем информацию о Ñборщике - * 2) Загружаем информацию о callback-ах - * 3) Собираем только те файлы, у которых еÑть callback и Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð´Ð»Ñ Ñборки - *----------------------------------------------------------------------------------------------------------------------- - *------------------------------------------Переподключение при разрыве ÑоединениÑ--------------------------------------- - * 1) ПроверÑем файл на Ñборку. ЕÑли она началаÑÑŒ, то оÑтанавливаем. - * 2) ЕÑли Ñборка уже завершилаÑÑŒ, то отправлÑем пользователю уведомление о невозможноÑти редактировать дальше - * 3) Далее проверÑем Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð¾Ñледнего ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð¸ lock-и пользователÑ. ЕÑли кто-то уже уÑпел Ñохранить или - * заблокировать объекты, то мы не можем дальше редактировать. - *----------------------------------------------------------------------------------------------------------------------- + -------------------------------------------------- --view-mode----------------------------------------------------- ------------ + * 1) For the view mode, we update the page (without a quick transition) so that the user is not considered editable and does not + * held the document for assembly (if you do not wait, then the quick transition from view to edit is incomprehensible when the document has already been assembled) + * 2) If the user is in view mode, then he does not participate in editing (only in chat). When opened, it receives + * all current changes in the document at the time of opening. For view-mode we do not accept changes and do not send them + * view-users (because it is not clear what to do in a situation where 1-user has made changes, + * saved and made undo). + *---------------------------------------------------------------- -------------------------------------------------- -------------------- + *------------------------------------------------Scheme save------------------------------------------------- ------ + * a) One user - the first time changes come without an index, then changes come with an index, you can do + * undo-redo (history is not rubbed). If autosave is enabled, then it is for any action (no more than 5 seconds). + * b) As soon as the second user enters, co-editing begins. A lock is placed on the document so that + * the first user managed to save the document (or send unlock) + * c) When there are 2 or more users, each save rubs the history and is sent in its entirety (no index). If + * autosave is enabled, it is saved no more than once every 10 minutes. + * d) When the user is left alone, after accepting someone else's changes, point 'a' begins + *---------------------------------------------------------------- -------------------------------------------------- -------------------- + *-------------------------------------------- Scheme of working with the server- -------------------------------------------------- - + * a) When everyone leaves, after the cfgAscSaveTimeOutDelay time, the assembly command is sent to the document server. + * b) If the status '1' comes to CommandService.ashx, then it was possible to save and raise the version. Clear callbacks and + * changes from base and from memory. + * c) If a status other than '1' arrives (this can include both the generation of the file and the work of an external subscriber + * with the finished result), then three callbacks, and leave the changes. Because you can go to the old + * version and get uncompiled changes. We also reset the status of the file to unassembled so that it can be + * open without version error message. + *---------------------------------------------------------------- -------------------------------------------------- -------------------- + *------------------------------------------------Start server------------------------------------------------- --------- + * 1) Loading information about the collector + * 2) Loading information about callbacks + * 3) We collect only those files that have a callback and information for building + *---------------------------------------------------------------- -------------------------------------------------- -------------------- + *------------------------------------------------Reconnect when disconnected--- ------------------------------------ + * 1) Check the file for assembly. If it starts, then stop. + * 2) If the assembly has already completed, then we send the user a notification that it is impossible to edit further + * 3) Next, check the time of the last save and lock-and user. If someone has already managed to save or + * lock objects, then we can't edit further. + *---------------------------------------------------------------- -------------------------------------------------- -------------------- * */ 'use strict'; -const sockjs = require('sockjs'); +const { Server } = require("socket.io"); const _ = require('underscore'); -const https = require('https'); -const http = require('http'); const url = require('url'); -const fs = require('fs'); const os = require('os'); const cluster = require('cluster'); -const cron = require('cron'); +const crypto = require('crypto'); +const pathModule = require('path'); const co = require('co'); const jwt = require('jsonwebtoken'); -const jwa = require('jwa'); const ms = require('ms'); +const deepEqual = require('deep-equal'); +const bytes = require('bytes'); const storage = require('./../../Common/sources/storage-base'); -const logger = require('./../../Common/sources/logger'); const constants = require('./../../Common/sources/constants'); const utils = require('./../../Common/sources/utils'); +const utilsDocService = require('./utilsDocService'); const commonDefines = require('./../../Common/sources/commondefines'); const statsDClient = require('./../../Common/sources/statsdclient'); -const configCommon = require('config'); -const config = configCommon.get('services.CoAuthoring'); -const sqlBase = require('./baseConnector'); +const config = require('config'); +const sqlBase = require('./databaseConnectors/baseConnector'); const canvasService = require('./canvasservice'); const converterService = require('./converterservice'); const taskResult = require('./taskresult'); -const redis = require(config.get('redis.name')); -const pubsubRedis = require('./pubsubRedis'); -const pubsubService = require('./' + config.get('pubsub.name')); +const gc = require('./gc'); +const shutdown = require('./shutdown'); +const pubsubService = require('./pubsubRabbitMQ'); +const wopiClient = require('./wopiClient'); const queueService = require('./../../Common/sources/taskqueueRabbitMQ'); -const rabbitMQCore = require('./../../Common/sources/rabbitMQCore'); -const cfgSpellcheckerUrl = config.get('server.editor_settings_spellchecker_url'); -const cfgCallbackRequestTimeout = config.get('server.callbackRequestTimeout'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); +const { notificationTypes, ...notificationService } = require('../../Common/sources/notificationService'); + +const cfgEditorDataStorage = config.get('services.CoAuthoring.server.editorDataStorage'); +const cfgEditorStatStorage = config.get('services.CoAuthoring.server.editorStatStorage'); +const editorDataStorage = require('./' + cfgEditorDataStorage); +const editorStatStorage = require('./' + (cfgEditorStatStorage || cfgEditorDataStorage)); +const util = require("util"); + +const cfgEditSingleton = config.get('services.CoAuthoring.server.edit_singleton'); +const cfgEditor = config.get('services.CoAuthoring.editor'); +const cfgCallbackRequestTimeout = config.get('services.CoAuthoring.server.callbackRequestTimeout'); //The waiting time to document assembly when all out(not 0 in case of F5 in the browser) -const cfgAscSaveTimeOutDelay = config.get('server.savetimeoutdelay'); - -const cfgPubSubMaxChanges = config.get('pubsub.maxChanges'); - -const cfgRedisPrefix = config.get('redis.prefix'); -const cfgExpSaveLock = config.get('expire.saveLock'); -const cfgExpPresence = config.get('expire.presence'); -const cfgExpLocks = config.get('expire.locks'); -const cfgExpChangeIndex = config.get('expire.changeindex'); -const cfgExpLockDoc = config.get('expire.lockDoc'); -const cfgExpMessage = config.get('expire.message'); -const cfgExpLastSave = config.get('expire.lastsave'); -const cfgExpForceSave = config.get('expire.forcesave'); -const cfgExpSaved = config.get('expire.saved'); -const cfgExpDocumentsCron = config.get('expire.documentsCron'); -const cfgExpSessionIdle = ms(config.get('expire.sessionidle')); -const cfgExpSessionAbsolute = ms(config.get('expire.sessionabsolute')); -const cfgExpSessionCloseCommand = ms(config.get('expire.sessionclosecommand')); -const cfgSockjsUrl = config.get('server.sockjsUrl'); -const cfgTokenEnableBrowser = config.get('token.enable.browser'); -const cfgTokenEnableRequestInbox = config.get('token.enable.request.inbox'); -const cfgTokenEnableRequestOutbox = config.get('token.enable.request.outbox'); -const cfgTokenSessionAlgorithm = config.get('token.session.algorithm'); -const cfgTokenSessionExpires = ms(config.get('token.session.expires')); -const cfgTokenInboxHeader = config.get('token.inbox.header'); -const cfgTokenInboxPrefix = config.get('token.inbox.prefix'); -const cfgSecretSession = config.get('secret.session'); -const cfgForceSaveEnable = config.get('autoAssembly.enable'); -const cfgForceSaveInterval = ms(config.get('autoAssembly.interval')); -const cfgForceSaveStep = ms(config.get('autoAssembly.step')); -const cfgQueueRetentionPeriod = configCommon.get('queue.retentionPeriod'); - -const redisKeySaveLock = cfgRedisPrefix + constants.REDIS_KEY_SAVE_LOCK; -const redisKeyPresenceHash = cfgRedisPrefix + constants.REDIS_KEY_PRESENCE_HASH; -const redisKeyPresenceSet = cfgRedisPrefix + constants.REDIS_KEY_PRESENCE_SET; -const redisKeyLocks = cfgRedisPrefix + constants.REDIS_KEY_LOCKS; -const redisKeyChangeIndex = cfgRedisPrefix + constants.REDIS_KEY_CHANGES_INDEX; -const redisKeyLockDoc = cfgRedisPrefix + constants.REDIS_KEY_LOCK_DOCUMENT; -const redisKeyMessage = cfgRedisPrefix + constants.REDIS_KEY_MESSAGE; -const redisKeyDocuments = cfgRedisPrefix + constants.REDIS_KEY_DOCUMENTS; -const redisKeyLastSave = cfgRedisPrefix + constants.REDIS_KEY_LAST_SAVE; -const redisKeyForceSave = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE; -const redisKeyForceSaveTimer = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE_TIMER; -const redisKeyForceSaveTimerLock = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE_TIMER_LOCK; -const redisKeySaved = cfgRedisPrefix + constants.REDIS_KEY_SAVED; +const cfgAscSaveTimeOutDelay = config.get('services.CoAuthoring.server.savetimeoutdelay'); + +const cfgPubSubMaxChanges = config.get('services.CoAuthoring.pubsub.maxChanges'); + +const cfgExpSaveLock = config.get('services.CoAuthoring.expire.saveLock'); +const cfgExpLockDoc = config.get('services.CoAuthoring.expire.lockDoc'); +const cfgExpSessionIdle = config.get('services.CoAuthoring.expire.sessionidle'); +const cfgExpSessionAbsolute = config.get('services.CoAuthoring.expire.sessionabsolute'); +const cfgExpSessionCloseCommand = config.get('services.CoAuthoring.expire.sessionclosecommand'); +const cfgExpUpdateVersionStatus = config.get('services.CoAuthoring.expire.updateVersionStatus'); +const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); +const cfgTokenEnableRequestInbox = config.get('services.CoAuthoring.token.enable.request.inbox'); +const cfgTokenSessionAlgorithm = config.get('services.CoAuthoring.token.session.algorithm'); +const cfgTokenSessionExpires = config.get('services.CoAuthoring.token.session.expires'); +const cfgTokenInboxHeader = config.get('services.CoAuthoring.token.inbox.header'); +const cfgTokenInboxPrefix = config.get('services.CoAuthoring.token.inbox.prefix'); +const cfgTokenVerifyOptions = config.get('services.CoAuthoring.token.verifyOptions'); +const cfgForceSaveEnable = config.get('services.CoAuthoring.autoAssembly.enable'); +const cfgForceSaveInterval = config.get('services.CoAuthoring.autoAssembly.interval'); +const cfgQueueRetentionPeriod = config.get('queue.retentionPeriod'); +const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles'); +const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname'); +const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestChanges'); +const cfgWarningLimitPercents = config.get('license.warning_limit_percents'); +const cfgNotificationRuleLicenseLimitEdit = config.get('notification.rules.licenseLimitEdit.template'); +const cfgNotificationRuleLicenseLimitLiveViewer = config.get('notification.rules.licenseLimitLiveViewer.template'); +const cfgErrorFiles = config.get('FileConverter.converter.errorfiles'); +const cfgOpenProtectedFile = config.get('services.CoAuthoring.server.openProtectedFile'); +const cfgIsAnonymousSupport = config.get('services.CoAuthoring.server.isAnonymousSupport'); +const cfgTokenRequiredParams = config.get('services.CoAuthoring.server.tokenRequiredParams'); +const cfgImageSize = config.get('services.CoAuthoring.server.limits_image_size'); +const cfgTypesUpload = config.get('services.CoAuthoring.utils.limits_image_types_upload'); +const cfgForceSaveUsingButtonWithoutChanges = config.get('services.CoAuthoring.server.forceSaveUsingButtonWithoutChanges'); +//todo tenant +const cfgExpDocumentsCron = config.get('services.CoAuthoring.expire.documentsCron'); +const cfgRefreshLockInterval = ms(config.get('wopi.refreshLockInterval')); +const cfgSocketIoConnection = config.get('services.CoAuthoring.socketio.connection'); +const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult'); +const cfgTableChanges = config.get('services.CoAuthoring.sql.tableChanges'); const EditorTypes = { document : 0, spreadsheet : 1, - presentation : 2 + presentation : 2, + diagram : 3 }; -const defaultHttpPort = 80, defaultHttpsPort = 443; // Порты по умолчанию (Ð´Ð»Ñ http и https) -const redisClient = pubsubRedis.getClientRedis(); +const defaultHttpPort = 80, defaultHttpsPort = 443; // Default ports (for http and https) +//todo remove editorDataStorage constructor usage after 8.1 +const editorData = editorDataStorage.EditorData ? new editorDataStorage.EditorData() : new editorDataStorage(); +const editorStat = editorStatStorage.EditorStat ? new editorStatStorage.EditorStat() : new editorDataStorage(); const clientStatsD = statsDClient.getClient(); -let connections = []; // Ðктивные ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ +let connections = []; // Active connections +let lockDocumentsTimerId = {};//to drop connection that can't unlockDocument let pubsub; let queue; -let licenseInfo = {type: constants.LICENSE_RESULT.Error, light: false, branding: false}; let shutdownFlag = false; +let expDocumentsStep = gc.getCronStep(cfgExpDocumentsCron); const MIN_SAVE_EXPIRATION = 60000; -const FORCE_SAVE_EXPIRATION = Math.min(Math.max(cfgForceSaveInterval, MIN_SAVE_EXPIRATION), - cfgQueueRetentionPeriod * 1000); const HEALTH_CHECK_KEY_MAX = 10000; +const SHARD_ID = crypto.randomBytes(16).toString('base64');//16 as guid + +const PRECISION = [{name: 'hour', val: ms('1h')}, {name: 'day', val: ms('1d')}, {name: 'week', val: ms('7d')}, + {name: 'month', val: ms('31d')}, +]; function getIsShutdown() { return shutdownFlag; } +function getEditorConfig(ctx) { + let tenEditor = ctx.getCfg('services.CoAuthoring.editor', cfgEditor); + tenEditor = JSON.parse(JSON.stringify(tenEditor)); + tenEditor['reconnection']['delay'] = ms(tenEditor['reconnection']['delay']); + tenEditor['websocketMaxPayloadSize'] = bytes.parse(tenEditor['websocketMaxPayloadSize']); + tenEditor['maxChangesSize'] = bytes.parse(tenEditor['maxChangesSize']); + return tenEditor; +} +function getForceSaveExpiration(ctx) { + const tenForceSaveInterval = ms(ctx.getCfg('services.CoAuthoring.autoAssembly.interval', cfgForceSaveInterval)); + const tenQueueRetentionPeriod = ctx.getCfg('queue.retentionPeriod', cfgQueueRetentionPeriod); + + return Math.min(Math.max(tenForceSaveInterval, MIN_SAVE_EXPIRATION), tenQueueRetentionPeriod * 1000); +} + function DocumentChanges(docId) { this.docId = docId; this.arrChanges = []; @@ -214,7 +239,7 @@ const c_oAscChangeBase = { All: 2 }; -const c_oAscLockTimeOutDelay = 500; // Ð’Ñ€ÐµÐ¼Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸Ñ Ð´Ð»Ñ ÑохранениÑ, когда зажата база данных +const c_oAscLockTimeOutDelay = 500; // Timeout to save when database is clamped const c_oAscRecalcIndexTypes = { RecalcIndexAdd: 1, @@ -226,11 +251,11 @@ const c_oAscRecalcIndexTypes = { * @const */ const c_oAscLockTypes = { - kLockTypeNone: 1, // никто не залочил данный объект - kLockTypeMine: 2, // данный объект залочен текущим пользователем - kLockTypeOther: 3, // данный объект залочен другим(не текущим) пользователем - kLockTypeOther2: 4, // данный объект залочен другим(не текущим) пользователем (Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ ÑƒÐ¶Ðµ пришли) - kLockTypeOther3: 5 // данный объект был залочен (Ð¾Ð±Ð½Ð¾Ð²Ð»ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ÑˆÐ»Ð¸) и Ñнова Ñтал залочен + kLockTypeNone: 1, // no one has locked this object + kLockTypeMine: 2, // this object is locked by the current user + kLockTypeOther: 3, // this object is locked by another (not the current) user + kLockTypeOther2: 4, // this object is locked by another (not the current) user (updates have already arrived) + kLockTypeOther3: 5 // this object has been locked (updates have arrived) and is now locked again }; const c_oAscLockTypeElem = { @@ -257,10 +282,10 @@ function CRecalcIndexElement(recalcType, position, bIsSaveIndex) { return new CRecalcIndexElement(recalcType, position, bIsSaveIndex); } - this._recalcType = recalcType; // Тип изменений (удаление или добавление) - this._position = position; // ПозициÑ, в которой произошли Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ - this._count = 1; // Считаем вÑе Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð·Ð° проÑтейшие - this.m_bIsSaveIndex = !!bIsSaveIndex; // Это индекÑÑ‹ из изменений других пользователей (которые мы еще не применили) + this._recalcType = recalcType; // Type of changes (removal or addition) + this._position = position; // The position where the changes happened + this._count = 1; // We consider all changes as the simplest + this.m_bIsSaveIndex = !!bIsSaveIndex; // These are indexes from other users' changes (that we haven't applied yet) return this; } @@ -268,20 +293,20 @@ function CRecalcIndexElement(recalcType, position, bIsSaveIndex) { CRecalcIndexElement.prototype = { constructor: CRecalcIndexElement, - // ПереÑчет Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… + // recalculate for others getLockOther: function(position, type) { var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? +1 : -1; if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && true === this.m_bIsSaveIndex) { - // Мы еще не применили чужие Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ (поÑтому Ð´Ð»Ñ insert не нужно отриÑовывать) - // RecalcIndexRemove (потому что перевертываем Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¾Ð¹ отработки, от другого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - // пришло RecalcIndexAdd + // We haven't applied someone else's changes yet (so insert doesn't need to be rendered) + // RecalcIndexRemove (because we flip it for proper processing, from another user + // RecalcIndexAdd arrived return null; } else if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && c_oAscLockTypes.kLockTypeMine === type && false === this.m_bIsSaveIndex) { - // Ð”Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ, который удалил Ñтолбец, риÑовать залоченные ранее в данном Ñтолбце Ñчейки - // не нужно + // For the user who deleted the column, draw previously locked cells in this column + // no need return null; } else if (position < this._position) { return position; @@ -290,7 +315,7 @@ CRecalcIndexElement.prototype = { return (position + inc); } }, - // ПереÑчет Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… (только Ð´Ð»Ñ ÑохранениÑ) + // Recalculation for others (save only) getLockSaveOther: function(position, type) { if (this.m_bIsSaveIndex) { return position; @@ -299,15 +324,15 @@ CRecalcIndexElement.prototype = { var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? +1 : -1; if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && true === this.m_bIsSaveIndex) { - // Мы еще не применили чужие Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ (поÑтому Ð´Ð»Ñ insert не нужно отриÑовывать) - // RecalcIndexRemove (потому что перевертываем Ð´Ð»Ñ Ð¿Ñ€Ð°Ð²Ð¸Ð»ÑŒÐ½Ð¾Ð¹ отработки, от другого Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - // пришло RecalcIndexAdd + // We haven't applied someone else's changes yet (so insert doesn't need to be rendered) + // RecalcIndexRemove (because we flip it for proper processing, from another user + // RecalcIndexAdd arrived return null; } else if (position === this._position && c_oAscRecalcIndexTypes.RecalcIndexRemove === this._recalcType && c_oAscLockTypes.kLockTypeMine === type && false === this.m_bIsSaveIndex) { - // Ð”Ð»Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ, который удалил Ñтолбец, риÑовать залоченные ранее в данном Ñтолбце Ñчейки - // не нужно + // For the user who deleted the column, draw previously locked cells in this column + // no need return null; } else if (position < this._position) { return position; @@ -316,7 +341,7 @@ CRecalcIndexElement.prototype = { return (position + inc); } }, - // ПереÑчет Ð´Ð»Ñ ÑÐµÐ±Ñ + // recalculate for ourselves getLockMe: function(position) { var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? -1 : +1; if (position < this._position) { @@ -326,7 +351,7 @@ CRecalcIndexElement.prototype = { return (position + inc); } }, - // Только когда от других пользователей Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ (Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑчета) + // Only when other users change (for recalculation) getLockMe2: function(position) { var inc = (c_oAscRecalcIndexTypes.RecalcIndexAdd === this._recalcType) ? -1 : +1; if (true !== this.m_bIsSaveIndex || position < this._position) { @@ -343,7 +368,7 @@ function CRecalcIndex() { return new CRecalcIndex(); } - this._arrElements = []; // МаÑÑив CRecalcIndexElement + this._arrElements = []; // CRecalcIndexElement array return this; } @@ -358,7 +383,6 @@ CRecalcIndex.prototype = { this._arrElements.length = 0; }, - // ПереÑчет Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… getLockOther: function(position, type) { var newPosition = position; var count = this._arrElements.length; @@ -371,7 +395,7 @@ CRecalcIndex.prototype = { return newPosition; }, - // ПереÑчет Ð´Ð»Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… (только Ð´Ð»Ñ ÑохранениÑ) + // Recalculation for others (save only) getLockSaveOther: function(position, type) { var newPosition = position; var count = this._arrElements.length; @@ -384,7 +408,7 @@ CRecalcIndex.prototype = { return newPosition; }, - // ПереÑчет Ð´Ð»Ñ ÑÐµÐ±Ñ + // recalculate for ourselves getLockMe: function(position) { var newPosition = position; var count = this._arrElements.length; @@ -397,7 +421,7 @@ CRecalcIndex.prototype = { return newPosition; }, - // Только когда от других пользователей Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ (Ð´Ð»Ñ Ð¿ÐµÑ€ÐµÑчета) + // Only when other users change (for recalculation) getLockMe2: function(position) { var newPosition = position; var count = this._arrElements.length; @@ -412,29 +436,157 @@ CRecalcIndex.prototype = { } }; -function sendData(conn, data) { - conn.write(JSON.stringify(data)); +function updatePresenceCounters(ctx, conn, val) { + return co(function* () { + let aggregationCtx; + if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) { + //aggregated server stats + aggregationCtx = new operationContext.Context(); + aggregationCtx.init(tenantManager.getDefautTenant(), ctx.docId, ctx.userId); + //yield ctx.initTenantCache(); //no need.only global config + } + if (utils.isLiveViewer(conn)) { + yield editorStat.incrLiveViewerConnectionsCountByShard(ctx, SHARD_ID, val); + if (aggregationCtx) { + yield editorStat.incrLiveViewerConnectionsCountByShard(aggregationCtx, SHARD_ID, val); + } + if (clientStatsD) { + let countLiveView = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); + clientStatsD.gauge('expireDoc.connections.liveview', countLiveView); + } + } else if (conn.isCloseCoAuthoring || (conn.user && conn.user.view)) { + yield editorStat.incrViewerConnectionsCountByShard(ctx, SHARD_ID, val); + if (aggregationCtx) { + yield editorStat.incrViewerConnectionsCountByShard(aggregationCtx, SHARD_ID, val); + } + if (clientStatsD) { + let countView = yield editorStat.getViewerConnectionsCount(ctx, connections); + clientStatsD.gauge('expireDoc.connections.view', countView); + } + } else { + yield editorStat.incrEditorConnectionsCountByShard(ctx, SHARD_ID, val); + if (aggregationCtx) { + yield editorStat.incrEditorConnectionsCountByShard(aggregationCtx, SHARD_ID, val); + } + if (clientStatsD) { + let countEditors = yield editorStat.getEditorConnectionsCount(ctx, connections); + clientStatsD.gauge('expireDoc.connections.edit', countEditors); + } + } + }); +} +function addPresence(ctx, conn, updateCunters) { + return co(function* () { + yield editorData.addPresence(ctx, conn.docId, conn.user.id, utils.getConnectionInfoStr(conn)); + if (updateCunters) { + yield updatePresenceCounters(ctx, conn, 1); + } + }); +} +async function updatePresence(ctx, conn) { + if (editorData.updatePresence) { + return await editorData.updatePresence(ctx, conn.docId, conn.user.id); + } else { + //todo remove if after 7.6. code for backward compatibility, because redis in separate repo + return await editorData.addPresence(ctx, conn.docId, conn.user.id, utils.getConnectionInfoStr(conn)); + } +} +function removePresence(ctx, conn) { + return co(function* () { + yield editorData.removePresence(ctx, conn.docId, conn.user.id); + yield updatePresenceCounters(ctx, conn, -1); + }); +} + +let changeConnectionInfo = co.wrap(function*(ctx, conn, cmd) { + if (!conn.denyChangeName && conn.user) { + yield publish(ctx, {type: commonDefines.c_oPublishType.changeConnecitonInfo, ctx: ctx, docId: conn.docId, useridoriginal: conn.user.idOriginal, cmd: cmd}); + return true; + } + return false; +}); +function signToken(ctx, payload, algorithm, expiresIn, secretElem) { + return co(function*() { + var options = {algorithm: algorithm, expiresIn: expiresIn}; + let secret = yield tenantManager.getTenantSecret(ctx, secretElem); + return jwt.sign(payload, secret, options); + }); +} +function needSendChanges (conn){ + return !conn.user?.view || utils.isLiveViewer(conn); +} +function fillJwtByConnection(ctx, conn) { + return co(function*() { + const tenTokenSessionAlgorithm = ctx.getCfg('services.CoAuthoring.token.session.algorithm', cfgTokenSessionAlgorithm); + const tenTokenSessionExpires = ms(ctx.getCfg('services.CoAuthoring.token.session.expires', cfgTokenSessionExpires)); + + var payload = {document: {}, editorConfig: {user: {}}}; + var doc = payload.document; + doc.key = conn.docId; + doc.permissions = conn.permissions; + doc.ds_encrypted = conn.encrypted; + var edit = payload.editorConfig; + //todo + //edit.callbackUrl = callbackUrl; + //edit.lang = conn.lang; + //edit.mode = conn.mode; + var user = edit.user; + user.id = conn.user.idOriginal; + user.name = conn.user.username; + user.index = conn.user.indexUser; + if (conn.coEditingMode) { + edit.coEditing = {mode: conn.coEditingMode}; + } + //no standart + edit.ds_isCloseCoAuthoring = conn.isCloseCoAuthoring; + edit.ds_isEnterCorrectPassword = conn.isEnterCorrectPassword; + // presenter viewer opens with same session jwt. do not put sessionId to jwt + // edit.ds_sessionId = conn.sessionId; + edit.ds_sessionTimeConnect = conn.sessionTimeConnect; + + return yield signToken(ctx, payload, tenTokenSessionAlgorithm, tenTokenSessionExpires / 1000, commonDefines.c_oAscSecretType.Session); + }); +} + +function sendData(ctx, conn, data) { + conn.emit('message', data); + const type = data ? data.type : null; + ctx.logger.debug('sendData: type = %s', type); +} +function sendDataWarning(ctx, conn, msg) { + sendData(ctx, conn, {type: "warning", message: msg}); +} +function sendDataMessage(ctx, conn, msg) { + if (!conn.permissions || false !== conn.permissions.chat) { + sendData(ctx, conn, {type: "message", messages: msg}); + } else { + ctx.logger.debug("sendDataMessage permissions.chat==false"); + } +} +function sendDataCursor(ctx, conn, msg) { + sendData(ctx, conn, {type: "cursor", messages: msg}); } -function sendDataWarning(conn, msg) { - sendData(conn, {type: "warning", message: msg}); +function sendDataMeta(ctx, conn, msg) { + sendData(ctx, conn, {type: "meta", messages: msg}); } -function sendDataMessage(conn, msg) { - sendData(conn, {type: "message", messages: msg}); +function sendDataSession(ctx, conn, msg) { + sendData(ctx, conn, {type: "session", messages: msg}); } -function sendDataCursor(conn, msg) { - sendData(conn, {type: "cursor", messages: msg}); +function sendDataRefreshToken(ctx, conn, msg) { + sendData(ctx, conn, {type: "refreshToken", messages: msg}); } -function sendDataMeta(conn, msg) { - sendData(conn, {type: "meta", messages: msg}); +function sendDataRpc(ctx, conn, responseKey, data) { + sendData(ctx, conn, {type: "rpc", responseKey: responseKey, data: data}); } -function sendDataSession(conn, msg) { - sendData(conn, {type: "session", messages: msg}); +function sendDataDrop(ctx, conn, code, description) { + sendData(ctx, conn, {type: "drop", code: code, description: description}); } -function sendDataRefreshToken(conn, msg) { - sendData(conn, {type: "refreshToken", messages: msg}); +function sendDataDisconnectReason(ctx, conn, code, description) { + sendData(ctx, conn, {type: "disconnectReason", code: code, description: description}); } -function sendReleaseLock(conn, userLocks) { - sendData(conn, {type: "releaseLock", locks: _.map(userLocks, function(e) { + +function sendReleaseLock(ctx, conn, userLocks) { + sendData(ctx, conn, {type: "releaseLock", locks: _.map(userLocks, function(e) { return { block: e.block, user: e.user, @@ -443,6 +595,24 @@ function sendReleaseLock(conn, userLocks) { }; })}); } +function modifyConnectionForPassword(ctx, conn, isEnterCorrectPassword) { + return co(function*() { + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + if (isEnterCorrectPassword) { + conn.isEnterCorrectPassword = true; + if (tenTokenEnableBrowser) { + let sessionToken = yield fillJwtByConnection(ctx, conn); + sendDataRefreshToken(ctx, conn, sessionToken); + } + } + }); +} +function modifyConnectionEditorToView(ctx, conn) { + if (conn.user) { + conn.user.view = true; + } + delete conn.coEditingMode; +} function getParticipants(docId, excludeClosed, excludeUserId, excludeViewer) { return _.filter(connections, function(el) { return el.docId === docId && el.isCloseCoAuthoring !== excludeClosed && @@ -454,90 +624,48 @@ function getParticipantUser(docId, includeUserId) { return el.docId === docId && el.user.id === includeUserId; }); } -function getConnectionInfo(conn) { - var user = conn.user; - var data = { - id: user.id, - idOriginal: user.idOriginal, - username: user.username, - indexUser: user.indexUser, - view: user.view, - connectionId: conn.id, - isCloseCoAuthoring: conn.isCloseCoAuthoring - }; - return JSON.stringify(data); -} -function updatePresenceCommandsToArray(outCommands, docId, userId, userInfo) { - var expireAt = new Date().getTime() + cfgExpPresence * 1000; - outCommands.push( - ['zadd', redisKeyPresenceSet + docId, expireAt, userId], - ['hset', redisKeyPresenceHash + docId, userId, userInfo], - ['expire', redisKeyPresenceSet + docId, cfgExpPresence], - ['expire', redisKeyPresenceHash + docId, cfgExpPresence] - ); -} -function* updatePresence(docId, userId, connInfo) { - var multi = redisClient.multi(getUpdatePresenceCommands(docId, userId, connInfo)); - yield utils.promiseRedis(multi, multi.exec); -} -function getUpdatePresenceCommands(docId, userId, connInfo) { - let commands = []; - updatePresenceCommandsToArray(commands, docId, userId, connInfo); - var expireAt = new Date().getTime() + cfgExpPresence * 1000; - commands.push(['zadd', redisKeyDocuments, expireAt, docId]); - return commands; -} -function* getAllPresence(docId, opt_userId, opt_connInfo) { - let now = (new Date()).getTime(); - let commands; - if(null != opt_userId && null != opt_connInfo){ - commands = getUpdatePresenceCommands(docId, opt_userId, opt_connInfo); + + +function* updateEditUsers(ctx, licenseInfo, userId, anonym, isLiveViewer) { + if (!licenseInfo.usersCount) { + return; + } + const now = new Date(); + const expireAt = (Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1)) / 1000 + + licenseInfo.usersExpire - 1; + let period = utils.getLicensePeriod(licenseInfo.startDate, now); + if (isLiveViewer) { + yield editorStat.addPresenceUniqueViewUser(ctx, userId, expireAt, {anonym: anonym}); + yield editorStat.addPresenceUniqueViewUsersOfMonth(ctx, userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); } else { - commands = []; - } - commands.push(['zrangebyscore', redisKeyPresenceSet + docId, 0, now], ['hvals', redisKeyPresenceHash + docId]); - let multi = redisClient.multi(commands); - let multiRes = yield utils.promiseRedis(multi, multi.exec); - let expiredKeys = multiRes[multiRes.length - 2]; - let hvals = multiRes[multiRes.length - 1]; - if (expiredKeys.length > 0) { - commands = [ - ['zremrangebyscore', redisKeyPresenceSet + docId, 0, now] - ]; - let expiredKeysMap = {}; - for (let i = 0; i < expiredKeys.length; ++i) { - let expiredKey = expiredKeys[i]; - expiredKeysMap[expiredKey] = 1; - commands.push(['hdel', redisKeyPresenceHash + docId, expiredKey]); - } - multi = redisClient.multi(commands); - yield utils.promiseRedis(multi, multi.exec); - hvals = hvals.filter(function(curValue) { - return null == expiredKeysMap[curValue]; - }) - } - return hvals; -} -function* hasEditors(docId, opt_hvals) { - var elem, hasEditors = false; + yield editorStat.addPresenceUniqueUser(ctx, userId, expireAt, {anonym: anonym}); + yield editorStat.addPresenceUniqueUsersOfMonth(ctx, userId, period, {anonym: anonym, firstOpenDate: now.toISOString()}); + } +} +function* getEditorsCount(ctx, docId, opt_hvals) { + var elem, editorsCount = 0; var hvals; if(opt_hvals){ hvals = opt_hvals; } else { - hvals = yield* getAllPresence(docId); + hvals = yield editorData.getPresence(ctx, docId, connections); } for (var i = 0; i < hvals.length; ++i) { elem = JSON.parse(hvals[i]); if(!elem.view && !elem.isCloseCoAuthoring) { - hasEditors = true; + editorsCount++; break; } } - return hasEditors; + return editorsCount; +} +function* hasEditors(ctx, docId, opt_hvals) { + let editorsCount = yield* getEditorsCount(ctx, docId, opt_hvals); + return editorsCount > 0; } -function* isUserReconnect(docId, userId, connectionId) { +function* isUserReconnect(ctx, docId, userId, connectionId) { var elem; - var hvals = yield* getAllPresence(docId); + var hvals = yield editorData.getPresence(ctx, docId, connections); for (var i = 0; i < hvals.length; ++i) { elem = JSON.parse(hvals[i]); if (userId === elem.id && connectionId !== elem.connectionId) { @@ -546,36 +674,55 @@ function* isUserReconnect(docId, userId, connectionId) { } return false; } -function* publish(data, optDocId, optUserId, opt_pubsub) { + +let pubsubOnMessage = null;//todo move function +async function publish(ctx, data, optDocId, optUserId, opt_pubsub) { var needPublish = true; - if(optDocId && optUserId) { + let hvals; + if (optDocId && optUserId) { needPublish = false; - var hvals = yield* getAllPresence(optDocId); + hvals = await editorData.getPresence(ctx, optDocId, connections); for (var i = 0; i < hvals.length; ++i) { var elem = JSON.parse(hvals[i]); - if(optUserId != elem.id) { + if (optUserId != elem.id) { needPublish = true; break; } } } - if(needPublish) { + if (needPublish) { var msg = JSON.stringify(data); var realPubsub = opt_pubsub ? opt_pubsub : pubsub; - realPubsub.publish(msg); + //don't use pubsub if all connections are local + if (pubsubOnMessage && hvals && hvals.length === getLocalConnectionCount(ctx, optDocId)) { + ctx.logger.debug("pubsub locally"); + //todo send connections from getLocalConnectionCount to pubsubOnMessage + pubsubOnMessage(msg); + } else if(realPubsub) { + await realPubsub.publish(msg); + } } + return needPublish; } function* addTask(data, priority, opt_queue, opt_expiration) { var realQueue = opt_queue ? opt_queue : queue; yield realQueue.addTask(data, priority, opt_expiration); } +function* addResponse(data, opt_queue) { + var realQueue = opt_queue ? opt_queue : queue; + yield realQueue.addResponse(data); +} +function* addDelayed(data, ttl, opt_queue) { + var realQueue = opt_queue ? opt_queue : queue; + yield realQueue.addDelayed(data, ttl); +} function* removeResponse(data) { yield queue.removeResponse(data); } -function* getOriginalParticipantsId(docId) { +async function getOriginalParticipantsId(ctx, docId) { var result = [], tmpObject = {}; - var hvals = yield* getAllPresence(docId); + var hvals = await editorData.getPresence(ctx, docId, connections); for (var i = 0; i < hvals.length; ++i) { var elem = JSON.parse(hvals[i]); if (!elem.view && !elem.isCloseCoAuthoring) { @@ -588,23 +735,35 @@ function* getOriginalParticipantsId(docId) { return result; } -function* sendServerRequest(docId, uri, dataObject) { - logger.debug('postData request: docId = %s;url = %s;data = %j', docId, uri, dataObject); - var authorization; - if (cfgTokenEnableRequestOutbox) { - authorization = utils.fillJwtForRequest(dataObject); +async function sendServerRequest(ctx, uri, dataObject, opt_checkAndFixAuthorizationLength) { + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + const tenTokenEnableRequestInbox = ctx.getCfg('services.CoAuthoring.token.enable.request.inbox', cfgTokenEnableRequestInbox); + + ctx.logger.debug('postData request: url = %s;data = %j', uri, dataObject); + let auth; + if (utils.canIncludeOutboxAuthorization(ctx, uri)) { + let secret = await tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + let bodyToken = utils.fillJwtForRequest(ctx, dataObject, secret, true); + auth = utils.fillJwtForRequest(ctx, dataObject, secret, false); + let authLen = auth.length; + if (opt_checkAndFixAuthorizationLength && !opt_checkAndFixAuthorizationLength(auth, dataObject)) { + auth = utils.fillJwtForRequest(ctx, dataObject, secret, false); + ctx.logger.warn('authorization too large. Use body token instead. size reduced from %d to %d', authLen, auth.length); + } + dataObject.setToken(bodyToken); } - var res = yield utils.postRequestPromise(uri, JSON.stringify(dataObject), cfgCallbackRequestTimeout * 1000, authorization); - logger.debug('postData response: docId = %s;data = %s', docId, res); - return res; + let headers = {'Content-Type': 'application/json'}; + //isInJwtToken is true because callbackUrl is required field in jwt token + let postRes = await utils.postRequestPromise(ctx, uri, JSON.stringify(dataObject), undefined, undefined, tenCallbackRequestTimeout, auth, tenTokenEnableRequestInbox, headers); + ctx.logger.debug('postData response: data = %s', postRes.body); + return postRes.body; } -// ПарÑинг ÑÑылки -function parseUrl(callbackUrl) { +function parseUrl(ctx, callbackUrl) { var result = null; try { - //делать decodeURIComponent не нужно http://expressjs.com/en/4x/api.html#app.settings.table - //по умолчанию express иÑпользует 'query parser' = 'extended', но даже в 'simple' верÑии делаетÑÑ decode + //no need to do decodeURIComponent http://expressjs.com/en/4x/api.html#app.settings.table + //by default express uses 'query parser' = 'extended', but even in 'simple' version decode is done //percent-encoded characters within the query string will be assumed to use UTF-8 encoding var parseObject = url.parse(callbackUrl); var isHttps = 'https:' === parseObject.protocol; @@ -620,612 +779,1081 @@ function parseUrl(callbackUrl) { 'href': parseObject.href }; } catch (e) { - logger.error("error parseUrl %s:\r\n%s", callbackUrl, e.stack); + ctx.logger.error("error parseUrl %s: %s", callbackUrl, e.stack); result = null; } return result; } -function* getCallback(id) { +async function getCallback(ctx, id, opt_userIndex) { var callbackUrl = null; var baseUrl = null; - var selectRes = yield taskResult.select(id); + let wopiParams = null; + var selectRes = await taskResult.select(ctx, id); if (selectRes.length > 0) { var row = selectRes[0]; if (row.callback) { - callbackUrl = row.callback; + callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, opt_userIndex); + wopiParams = wopiClient.parseWopiCallback(ctx, callbackUrl, row.callback); } if (row.baseurl) { baseUrl = row.baseurl; } } if (null != callbackUrl && null != baseUrl) { - return {server: parseUrl(callbackUrl), baseUrl: baseUrl}; + return {server: parseUrl(ctx, callbackUrl), baseUrl: baseUrl, wopiParams: wopiParams}; } else { return null; } } -function* getChangesIndex(docId) { +function* getChangesIndex(ctx, docId) { var res = 0; - var redisRes = yield utils.promiseRedis(redisClient, redisClient.get, redisKeyChangeIndex + docId); - if (null != redisRes) { - res = parseInt(redisRes); - } else { - var getRes = yield sqlBase.getChangesIndexPromise(docId); - if (getRes && getRes.length > 0 && null != getRes[0]['change_id']) { - res = getRes[0]['change_id'] + 1; - } + var getRes = yield sqlBase.getChangesIndexPromise(ctx, docId); + if (getRes && getRes.length > 0 && null != getRes[0]['change_id']) { + res = getRes[0]['change_id'] + 1; } return res; } -function* getLastSave(docId) { - var res = yield utils.promiseRedis(redisClient, redisClient.hgetall, redisKeyLastSave + docId); - if (res) { - if (res.time) { - res.time = parseInt(res.time); + +const hasChanges = co.wrap(function*(ctx, docId) { + //todo check editorData.getForceSave in case of "undo all changes" + let puckerIndex = yield* getChangesIndex(ctx, docId); + if (0 === puckerIndex) { + let selectRes = yield taskResult.select(ctx, docId); + if (selectRes.length > 0 && selectRes[0].password) { + return sqlBase.DocumentPassword.prototype.hasPasswordChanges(ctx, selectRes[0].password); } - if (res.index) { - res.index = parseInt(res.index); + return false; + } + return true; +}); +function* setForceSave(ctx, docId, forceSave, cmd, success, url) { + let forceSaveType = forceSave.getType(); + let end = success; + if (commonDefines.c_oAscForceSaveTypes.Form === forceSaveType || commonDefines.c_oAscForceSaveTypes.Internal === forceSaveType) { + let forceSave = yield editorData.getForceSave(ctx, docId); + end = forceSave.ended; + } + let convertInfo = new commonDefines.InputCommand(cmd, true); + //remove request specific fields from cmd + convertInfo.setUserConnectionDocId(undefined); + convertInfo.setUserConnectionId(undefined); + convertInfo.setResponseKey(undefined); + convertInfo.setFormData(undefined); + if (convertInfo.getForceSave()) { + //type must be saved to distinguish c_oAscForceSaveTypes.Form + //convertInfo.getForceSave().setType(undefined); + convertInfo.getForceSave().setAuthorUserId(undefined); + convertInfo.getForceSave().setAuthorUserIndex(undefined); + } + yield editorData.checkAndSetForceSave(ctx, docId, forceSave.getTime(), forceSave.getIndex(), end, end, convertInfo); + + if (commonDefines.c_oAscForceSaveTypes.Command !== forceSaveType) { + let data = {type: forceSaveType, time: forceSave.getTime(), success: success}; + if(commonDefines.c_oAscForceSaveTypes.Form === forceSaveType || commonDefines.c_oAscForceSaveTypes.Internal === forceSaveType) { + let code = success ? commonDefines.c_oAscServerCommandErrors.NoError : commonDefines.c_oAscServerCommandErrors.UnknownError; + data = {code: code, time: forceSave.getTime(), inProgress: false}; + if (commonDefines.c_oAscForceSaveTypes.Internal === forceSaveType) { + data.url = url; + } + let userId = cmd.getUserConnectionId(); + docId = cmd.getUserConnectionDocId() || docId; + yield publish(ctx, {type: commonDefines.c_oPublishType.rpc, ctx, docId, userId, data, responseKey: cmd.getResponseKey()}); + } else { + yield publish(ctx, {type: commonDefines.c_oPublishType.forceSave, ctx: ctx, docId: docId, data: data}, cmd.getUserConnectionId()); } } - return res; } -function getForceSaveIndex(time, index) { - return time + '_' + index; -} -function* setForceSave(docId, forceSave, cmd, success) { - let forceSaveIndex = getForceSaveIndex(forceSave.getTime(), forceSave.getIndex()); - if (success) { - yield utils.promiseRedis(redisClient, redisClient.hset, redisKeyForceSave + docId, forceSaveIndex, true); - } else { - yield utils.promiseRedis(redisClient, redisClient.hdel, redisKeyForceSave + docId, forceSaveIndex); +async function checkForceSaveCache(ctx, convertInfo) { + let res = {hasCache: false, hasValidCache: false, cmd: null}; + if (convertInfo) { + res.hasCache = true; + let cmd = new commonDefines.InputCommand(convertInfo, true); + const saveKey = cmd.getDocId() + cmd.getSaveKey(); + const outputPath = cmd.getOutputPath(); + if (saveKey && outputPath) { + const savePathDoc = saveKey + '/' + outputPath; + const metadata = await storage.headObject(ctx, savePathDoc); + res.hasValidCache = !!metadata; + res.cmd = cmd; + } } - let forceSaveType = forceSave.getType(); - yield* publish({ - type: commonDefines.c_oPublishType.forceSave, docId: docId, - data: {type: forceSaveType, time: forceSave.getTime(), success: success} - }, cmd.getUserConnectionId()); + return res; } -function* getLastForceSave(docId, lastSave) { - let res = false; - if (lastSave) { - let forceSaveIndex = getForceSaveIndex(lastSave.time, lastSave.index); - let forceSave = yield utils.promiseRedis(redisClient, redisClient.hget, redisKeyForceSave + docId, forceSaveIndex); - if (forceSave) { - res = true; +async function applyForceSaveCache(ctx, docId, forceSave, type, opt_userConnectionId, opt_userConnectionDocId, + opt_responseKey, opt_formdata, opt_userId, opt_userIndex, opt_prevTime) { + let res = {ok: false, notModified: false, inProgress: false, startedForceSave: null}; + if (!forceSave) { + res.notModified = true; + return res; + } + let forceSaveCache = await checkForceSaveCache(ctx, forceSave.convertInfo); + if (forceSaveCache.hasCache || forceSave.ended) { + if (commonDefines.c_oAscForceSaveTypes.Form === type || commonDefines.c_oAscForceSaveTypes.Internal === type || !forceSave.ended) { + //c_oAscForceSaveTypes.Form has uniqueue options {'documentLayout': {'isPrint': true}}; dont use it for other types + let forceSaveCached = forceSaveCache.cmd?.getForceSave()?.getType(); + let cacheHasSameOptions = (commonDefines.c_oAscForceSaveTypes.Form === type && commonDefines.c_oAscForceSaveTypes.Form === forceSaveCached) || + (commonDefines.c_oAscForceSaveTypes.Form !== type && commonDefines.c_oAscForceSaveTypes.Form !== forceSaveCached); + if (forceSaveCache.hasValidCache && cacheHasSameOptions) { + if (commonDefines.c_oAscForceSaveTypes.Internal === type && forceSave.time === opt_prevTime) { + res.notModified = true; + } else { + let cmd = forceSaveCache.cmd; + cmd.setUserConnectionDocId(opt_userConnectionDocId); + cmd.setUserConnectionId(opt_userConnectionId); + cmd.setResponseKey(opt_responseKey); + cmd.setFormData(opt_formdata); + if (cmd.getForceSave()) { + cmd.getForceSave().setType(type); + cmd.getForceSave().setAuthorUserId(opt_userId); + cmd.getForceSave().setAuthorUserIndex(opt_userIndex); + } + //todo timeout because commandSfcCallback make request? + await canvasService.commandSfcCallback(ctx, cmd, true, false); + res.ok = true; + } + } else { + await editorData.checkAndSetForceSave(ctx, docId, forceSave.time, forceSave.index, false, false, null); + res.startedForceSave = await editorData.checkAndStartForceSave(ctx, docId); + res.ok = !!res.startedForceSave; + } + } else { + res.notModified = true; } + } else if (!forceSave.started) { + res.startedForceSave = await editorData.checkAndStartForceSave(ctx, docId); + res.ok = !!res.startedForceSave; + return res; + } else if (commonDefines.c_oAscForceSaveTypes.Form === type || commonDefines.c_oAscForceSaveTypes.Internal === type) { + res.ok = true; + res.inProgress = true; + } else { + res.notModified = true; } return res; } -function* startForceSave(docId, type, opt_userdata, opt_userConnectionId, opt_baseUrl, opt_queue, opt_pubsub) { - logger.debug('startForceSave start:docId = %s', docId); - let res = {code: commonDefines.c_oAscServerCommandErrors.NoError, time: null}; - let lastSave = null; +async function startForceSave(ctx, docId, type, opt_userdata, opt_formdata, opt_userId, opt_userConnectionId, + opt_userConnectionDocId, opt_userIndex, opt_responseKey, opt_baseUrl, + opt_queue, opt_pubsub, opt_conn, opt_initShardKey, opt_jsonParams, opt_changeInfo, + opt_prevTime) { + const tenForceSaveUsingButtonWithoutChanges = ctx.getCfg('services.CoAuthoring.server.forceSaveUsingButtonWithoutChanges', cfgForceSaveUsingButtonWithoutChanges); + ctx.logger.debug('startForceSave start'); + let res = {code: commonDefines.c_oAscServerCommandErrors.NoError, time: null, inProgress: false}; + let startedForceSave; + let hasEncrypted = false; if (!shutdownFlag) { - lastSave = yield* getLastSave(docId); - if (lastSave && undefined !== lastSave.time && undefined !== lastSave.index) { - let forceSaveIndex = getForceSaveIndex(lastSave.time, lastSave.index); - let multi = redisClient.multi([ - ['hsetnx', redisKeyForceSave + docId, forceSaveIndex, false], - ['expire', redisKeyForceSave + docId, cfgExpForceSave] - ]); - let execRes = yield utils.promiseRedis(multi, multi.exec); - //hsetnx 0 if field already exists - if (0 == execRes[0]) { - lastSave = null; + let hvals = await editorData.getPresence(ctx, docId, connections); + hasEncrypted = hvals.some((currentValue) => { + return !!JSON.parse(currentValue).encrypted; + }); + if (!hasEncrypted) { + let forceSave = await editorData.getForceSave(ctx, docId); + let forceSaveWithConnection = opt_conn && (commonDefines.c_oAscForceSaveTypes.Form === type || + (commonDefines.c_oAscForceSaveTypes.Button === type && tenForceSaveUsingButtonWithoutChanges)); + let startWithoutChanges = !forceSave && (forceSaveWithConnection || opt_changeInfo); + if (startWithoutChanges) { + //stub to send forms without changes + let newChangesLastDate = new Date(); + newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding + let newChangesLastTime = newChangesLastDate.getTime(); + let baseUrl = opt_baseUrl || ""; + let changeInfo = opt_changeInfo; + if (opt_conn) { + baseUrl = utils.getBaseUrlByConnection(ctx, opt_conn); + changeInfo = getExternalChangeInfo(opt_conn.user, newChangesLastTime, opt_conn.lang); + } + await editorData.setForceSave(ctx, docId, newChangesLastTime, 0, baseUrl, changeInfo, null); + forceSave = await editorData.getForceSave(ctx, docId); } - } else { - lastSave = null; + let applyCacheRes = await applyForceSaveCache(ctx, docId, forceSave, type, opt_userConnectionId, + opt_userConnectionDocId, opt_responseKey, opt_formdata, opt_userId, opt_userIndex, opt_prevTime); + startedForceSave = applyCacheRes.startedForceSave; + if (applyCacheRes.notModified) { + let selectRes = await taskResult.select(ctx, docId); + if (selectRes.length > 0) { + res.code = commonDefines.c_oAscServerCommandErrors.NotModified; + } else { + res.code = commonDefines.c_oAscServerCommandErrors.DocumentIdError; + } + } else if (!applyCacheRes.ok) { + res.code = commonDefines.c_oAscServerCommandErrors.UnknownError; + } + res.inProgress = applyCacheRes.inProgress; } } - if (lastSave) { - logger.debug('startForceSave lastSave:docId = %s; lastSave = %j', docId, lastSave); - let baseUrl = opt_baseUrl || lastSave.baseUrl; - let forceSave = new commonDefines.CForceSaveData(lastSave); - forceSave.setType(type); - if (commonDefines.c_oAscForceSaveTypes.Button !== type) { - yield* publish({ - type: commonDefines.c_oPublishType.forceSave, docId: docId, - data: {type: type, time: forceSave.getTime(), start: true} - }, undefined, undefined, opt_pubsub); - } + ctx.logger.debug('startForceSave canStart: hasEncrypted = %s; applyCacheRes = %j; startedForceSave = %j', hasEncrypted, res, startedForceSave); + if (startedForceSave) { + let baseUrl = opt_baseUrl || startedForceSave.baseUrl; + let forceSave = new commonDefines.CForceSaveData(startedForceSave); + forceSave.setType(type); + forceSave.setAuthorUserId(opt_userId); + forceSave.setAuthorUserIndex(opt_userIndex); let priority; let expiration; if (commonDefines.c_oAscForceSaveTypes.Timeout === type) { priority = constants.QUEUE_PRIORITY_VERY_LOW; - expiration = FORCE_SAVE_EXPIRATION; + expiration = getForceSaveExpiration(ctx); } else { priority = constants.QUEUE_PRIORITY_LOW; } //start new convert - let status = yield* converterService.convertFromChanges(docId, baseUrl, forceSave, opt_userdata, - opt_userConnectionId, priority, expiration, opt_queue); + let status = await converterService.convertFromChanges(ctx, docId, baseUrl, forceSave, startedForceSave.changeInfo, + opt_userdata, opt_formdata, opt_userConnectionId, opt_userConnectionDocId, opt_responseKey, priority, expiration, + opt_queue, undefined, opt_initShardKey, opt_jsonParams); if (constants.NO_ERROR === status.err) { res.time = forceSave.getTime(); + if (commonDefines.c_oAscForceSaveTypes.Timeout === type) { + await publish(ctx, { + type: commonDefines.c_oPublishType.forceSave, ctx: ctx, docId: docId, + data: {type: type, time: forceSave.getTime(), start: true} + }, undefined, undefined, opt_pubsub); + } } else { res.code = commonDefines.c_oAscServerCommandErrors.UnknownError; } - logger.debug('startForceSave convertFromChanges:docId = %s; status = %d', docId, status.err); - } else { - res.code = commonDefines.c_oAscServerCommandErrors.NotModified; - logger.debug('startForceSave NotModified no changes:docId = %s', docId); + ctx.logger.debug('startForceSave convertFromChanges: status = %d', status.err); + } + ctx.logger.debug('startForceSave end'); + return res; +} +function getExternalChangeInfo(user, date, lang) { + return {user_id: user.id, user_id_original: user.idOriginal, user_name: user.username, lang, change_date: date}; +} +let resetForceSaveAfterChanges = co.wrap(function*(ctx, docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo) { + const tenForceSaveEnable = ctx.getCfg('services.CoAuthoring.autoAssembly.enable', cfgForceSaveEnable); + const tenForceSaveInterval = ms(ctx.getCfg('services.CoAuthoring.autoAssembly.interval', cfgForceSaveInterval)); + //last save + if (newChangesLastTime) { + yield editorData.setForceSave(ctx, docId, newChangesLastTime, puckerIndex, baseUrl, changeInfo, null); + if (tenForceSaveEnable) { + let expireAt = newChangesLastTime + tenForceSaveInterval; + yield editorData.addForceSaveTimerNX(ctx, docId, expireAt); + } + } +}); +let saveRelativeFromChanges = co.wrap(function*(ctx, conn, responseKey, data) { + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + + let docId = data.docId; + let token = data.token; + let forceSaveRes; + if (tenTokenEnableBrowser) { + docId = null; + let checkJwtRes = yield checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser); + if (checkJwtRes.decoded) { + docId = checkJwtRes.decoded.key; + } else { + ctx.logger.warn('Error saveRelativeFromChanges jwt: %s', checkJwtRes.description); + forceSaveRes = {code: commonDefines.c_oAscServerCommandErrors.Token, time: null, inProgress: false}; + } + } + if (!forceSaveRes) { + forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Internal, undefined, undefined, undefined, conn.user.id, conn.docId, undefined, responseKey, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, data.time); + } + if (commonDefines.c_oAscServerCommandErrors.NoError !== forceSaveRes.code || forceSaveRes.inProgress) { + sendDataRpc(ctx, conn, responseKey, forceSaveRes); + } +}) + +async function startWopiRPC(ctx, docId, userId, userIdOriginal, data) { + let res; + let selectRes = await taskResult.select(ctx, docId); + let row = selectRes.length > 0 ? selectRes[0] : null; + if (row) { + if (row.callback) { + let userIndex = utils.getIndexFromUserId(userId, userIdOriginal); + let uri = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, userIndex); + let wopiParams = wopiClient.parseWopiCallback(ctx, uri, row.callback); + if (wopiParams) { + switch (data.type) { + case 'wopi_RenameFile': + res = await wopiClient.renameFile(ctx, wopiParams, data.name); + break; + case 'wopi_RefreshFile': + res = await wopiClient.refreshFile(ctx, wopiParams, row.baseurl); + break; + } + } + } } - logger.debug('startForceSave end:docId = %s', docId); return res; } -function handleDeadLetter(data) { +function* startRPC(ctx, conn, responseKey, data) { + let docId = conn.docId; + ctx.logger.debug('startRPC start responseKey:%s , %j', responseKey, data); + switch (data.type) { + case 'sendForm': { + let forceSaveRes; + if (conn.user) { + //isPrint - to remove forms + let jsonParams = {'documentLayout': {'isPrint': true}}; + forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Form, undefined, + data.formdata, conn.user.idOriginal, conn.user.id, undefined, conn.user.indexUser, + responseKey, undefined, undefined, undefined, conn, undefined, jsonParams); + } + if (!forceSaveRes || commonDefines.c_oAscServerCommandErrors.NoError !== forceSaveRes.code || forceSaveRes.inProgress) { + sendDataRpc(ctx, conn, responseKey, forceSaveRes); + } + break; + } + case 'saveRelativeFromChanges': { + yield saveRelativeFromChanges(ctx, conn, responseKey, data); + break; + } + case 'wopi_RenameFile': + case 'wopi_RefreshFile': { + let res = yield startWopiRPC(ctx, conn.docId, conn.user.id, conn.user.idOriginal, data); + sendDataRpc(ctx, conn, responseKey, res); + break; + } + case 'pathurls': + let outputData = new canvasService.OutputData(data.type); + yield* canvasService.commandPathUrls(ctx, conn, data.data, outputData); + sendDataRpc(ctx, conn, responseKey, outputData); + break; + } + ctx.logger.debug('startRPC end'); +} +function handleDeadLetter(data, ack) { return co(function*() { - let docId = 'null'; + let ctx = new operationContext.Context(); try { - logger.debug('handleDeadLetter start: docId = %s %s', docId, data); var isRequeued = false; let task = new commonDefines.TaskQueueData(JSON.parse(data)); if (task) { + ctx.initFromTaskQueueData(task); + yield ctx.initTenantCache(); let cmd = task.getCmd(); - docId = cmd.getDocId(); + ctx.logger.warn('handleDeadLetter start: %s', data); let forceSave = cmd.getForceSave(); - //todo requeue other tasks if (forceSave && commonDefines.c_oAscForceSaveTypes.Timeout == forceSave.getType()) { - let lastSave = yield* getLastSave(docId); + let actualForceSave = yield editorData.getForceSave(ctx, cmd.getDocId()); //check that there are no new changes - if (lastSave && forceSave.getTime() === lastSave.time && forceSave.getIndex() === lastSave.index) { + if (actualForceSave && forceSave.getTime() === actualForceSave.time && forceSave.getIndex() === actualForceSave.index) { //requeue task - yield* addTask(task, constants.QUEUE_PRIORITY_VERY_LOW, undefined, FORCE_SAVE_EXPIRATION); + yield* addTask(task, constants.QUEUE_PRIORITY_VERY_LOW, undefined, getForceSaveExpiration(ctx)); isRequeued = true; } + } else if (!forceSave && task.getFromChanges()) { + yield* addTask(task, constants.QUEUE_PRIORITY_NORMAL, undefined); + isRequeued = true; + } else if(cmd.getAttempt()) { + ctx.logger.warn('handleDeadLetter addResponse delayed = %d', cmd.getAttempt()); + yield* addResponse(task); + } else { + //simulate error response + cmd.setStatusInfo(constants.CONVERT_DEAD_LETTER); + canvasService.receiveTask(JSON.stringify(task), function(){}); } } - logger.debug('handleDeadLetter end: docId = %s; requeue = %s', docId, isRequeued); + ctx.logger.warn('handleDeadLetter end: requeue = %s', isRequeued); } catch (err) { - logger.debug('handleDeadLetter error: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('handleDeadLetter error: %s', err.stack); + } finally { + ack(); } }); } /** - * Отправка ÑтатуÑа, чтобы знать когда документ начал редактироватьÑÑ, а когда закончилÑÑ + * Sending status to know when the document started editing and when it ended * @param docId * @param {number} bChangeBase * @param callback * @param baseUrl */ -function* sendStatusDocument(docId, bChangeBase, userAction, callback, baseUrl, opt_userData) { - if (!callback) { - var getRes = yield* getCallback(docId); +async function sendStatusDocument(ctx, docId, bChangeBase, opt_userAction, opt_userIndex, opt_callback, opt_baseUrl, opt_userData, opt_forceClose) { + if (!opt_callback) { + var getRes = await getCallback(ctx, docId, opt_userIndex); if (getRes) { - callback = getRes.server; - if (!baseUrl) { - baseUrl = getRes.baseUrl; + opt_callback = getRes.server; + if (!opt_baseUrl) { + opt_baseUrl = getRes.baseUrl; + } + if (getRes.wopiParams) { + ctx.logger.debug('sendStatusDocument wopi stub'); + return opt_callback; } } } - if (null == callback) { + if (null == opt_callback) { return; } var status = c_oAscServerStatus.Editing; - var participants = yield* getOriginalParticipantsId(docId); + var participants = await getOriginalParticipantsId(ctx, docId); if (0 === participants.length) { - var puckerIndex = yield* getChangesIndex(docId); - if (!(puckerIndex > 0)) { + let bHasChanges = await hasChanges(ctx, docId); + if (!bHasChanges || opt_forceClose) { status = c_oAscServerStatus.Closed; } } if (c_oAscChangeBase.No !== bChangeBase) { - if (c_oAscServerStatus.Editing === status && c_oAscChangeBase.All === bChangeBase) { - // Добавить в базу - var updateMask = new taskResult.TaskResultData(); - updateMask.key = docId; - updateMask.callback = ''; - updateMask.baseurl = ''; + //update callback even if the connection is closed to avoid script: + //open->make changes->disconnect->subscription from community->reconnect + if (c_oAscChangeBase.All === bChangeBase) { + //always override callback to avoid expired callbacks var updateTask = new taskResult.TaskResultData(); + updateTask.tenant = ctx.tenant; updateTask.key = docId; - updateTask.callback = callback.href; - updateTask.baseurl = baseUrl; - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); + updateTask.callback = opt_callback.href; + updateTask.baseurl = opt_baseUrl; + var updateIfRes = await taskResult.update(ctx, updateTask); if (updateIfRes.affectedRows > 0) { - logger.debug('sendStatusDocument updateIf: docId = %s', docId); + ctx.logger.debug('sendStatusDocument updateIf'); } else { - logger.debug('sendStatusDocument updateIf no effect: docId = %s', docId); + ctx.logger.debug('sendStatusDocument updateIf no effect'); } } } - var sendData = new commonDefines.OutputSfcData(); - sendData.setKey(docId); + var sendData = new commonDefines.OutputSfcData(docId); sendData.setStatus(status); if (c_oAscServerStatus.Closed !== status) { sendData.setUsers(participants); } - if (userAction) { - sendData.setActions([userAction]); + if (opt_userAction) { + sendData.setActions([opt_userAction]); } if (opt_userData) { sendData.setUserData(opt_userData); } - var uri = callback.href; + var uri = opt_callback.href; var replyData = null; try { - replyData = yield* sendServerRequest(docId, uri, sendData); + replyData = await sendServerRequest(ctx, uri, sendData); } catch (err) { replyData = null; - logger.error('postData error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, sendData, err.stack); + ctx.logger.error('postData error: url = %s;data = %j %s', uri, sendData, err.stack); } - yield* onReplySendStatusDocument(docId, replyData); + await onReplySendStatusDocument(ctx, docId, replyData); + return sendData; } -function parseReplyData(docId, replyData) { +function parseReplyData(ctx, replyData) { var res = null; if (replyData) { try { res = JSON.parse(replyData); } catch (e) { - logger.error("error parseReplyData: docId = %s; data = %s\r\n%s", docId, replyData, e.stack); + ctx.logger.error("error parseReplyData: data = %s %s", replyData, e.stack); res = null; } } return res; } -function* onReplySendStatusDocument(docId, replyData) { - var oData = parseReplyData(docId, replyData); +let onReplySendStatusDocument = co.wrap(function*(ctx, docId, replyData) { + var oData = parseReplyData(ctx, replyData); if (!(oData && commonDefines.c_oAscServerCommandErrors.NoError == oData.error)) { - // Ошибка подпиÑки на callback, поÑылаем warning - yield* publish({type: commonDefines.c_oPublishType.warning, docId: docId, description: 'Error on save server subscription!'}); + // Error subscribing to callback, send warning + yield publish(ctx, {type: commonDefines.c_oPublishType.warning, ctx: ctx, docId: docId, description: 'Error on save server subscription!'}); } -} -function* dropUsersFromDocument(docId, users) { +}); +function* publishCloseUsersConnection(ctx, docId, users, isOriginalId, code, description) { if (Array.isArray(users)) { - yield* publish({type: commonDefines.c_oPublishType.drop, docId: docId, users: users, description: ''}); + let usersMap = users.reduce(function(map, val) { + map[val] = 1; + return map; + }, {}); + yield publish(ctx, { + type: commonDefines.c_oPublishType.closeConnection, ctx: ctx, docId: docId, usersMap: usersMap, + isOriginalId: isOriginalId, code: code, description: description + }); + } +} +function closeUsersConnection(ctx, docId, usersMap, isOriginalId, code, description) { + //close + let conn; + for (let i = connections.length - 1; i >= 0; --i) { + conn = connections[i]; + if (conn.docId === docId) { + if (isOriginalId ? usersMap[conn.user.idOriginal] : usersMap[conn.user.id]) { + sendDataDisconnectReason(ctx, conn, code, description); + conn.disconnect(true); + } + } } } +async function dropUsersFromDocument(ctx, docId, opt_users) { + await publish(ctx, {type: commonDefines.c_oPublishType.drop, ctx: ctx, docId: docId, users: opt_users, description: ''}); +} -function dropUserFromDocument(docId, userId, description) { +function dropUserFromDocument(ctx, docId, users, description) { var elConnection; for (var i = 0, length = connections.length; i < length; ++i) { elConnection = connections[i]; - if (elConnection.docId === docId && userId === elConnection.user.idOriginal && !elConnection.isCloseCoAuthoring) { - sendData(elConnection, - { - type: "drop", - description: description - });//Or 0 if fails + if (elConnection.docId === docId && !elConnection.isCloseCoAuthoring && (!users || users.includes(elConnection.user.idOriginal)) ) { + sendDataDrop(ctx, elConnection, description); } } } +function getLocalConnectionCount(ctx, docId) { + let tenant = ctx.tenant; + return connections.reduce(function(count, conn) { + if (conn.docId === docId && conn.tenant === ctx.tenant) { + count++; + } + return count; + }, 0); +} -// ПодпиÑка на Ñвенты: -function* bindEvents(docId, callback, baseUrl, opt_userAction, opt_userData) { - // ПодпиÑка на Ñвенты: - // - еÑли пользователей нет и изменений нет, то отÑылаем ÑÑ‚Ð°Ñ‚ÑƒÑ "закрыто" и в базу не добавлÑем - // - еÑли пользователей нет, а Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ ÐµÑть, то отÑылаем ÑÑ‚Ð°Ñ‚ÑƒÑ "редактируем" без пользователей, но добавлÑем в базу - // - еÑли еÑть пользователи, то проÑто добавлÑем в базу +// Event subscription: +function* bindEvents(ctx, docId, callback, baseUrl, opt_userAction, opt_userData) { + // Subscribe to events: + // - if there are no users and no changes, then send the status "closed" and do not add to the database + // - if there are no users, but there are changes, then send the "editing" status without users, but add it to the database + // - if there are users, then just add to the database var bChangeBase; var oCallbackUrl; - var getRes = yield* getCallback(docId); - if (getRes) { - oCallbackUrl = getRes.server; - bChangeBase = c_oAscChangeBase.Delete; + if (!callback) { + var getRes = yield getCallback(ctx, docId); + if (getRes && !getRes.wopiParams) { + oCallbackUrl = getRes.server; + bChangeBase = c_oAscChangeBase.Delete; + } } else { - oCallbackUrl = parseUrl(callback); - bChangeBase = c_oAscChangeBase.All; + oCallbackUrl = parseUrl(ctx, callback); + bChangeBase = c_oAscChangeBase.No; if (null !== oCallbackUrl) { - var hostIp = yield utils.dnsLookup(oCallbackUrl.host); - if (utils.checkIpFilter(hostIp, oCallbackUrl.host) > 0) { - logger.error('checkIpFilter error: docId = %s;url = %s', docId, callback); + let filterStatus = yield* utils.checkHostFilter(ctx, oCallbackUrl.host); + if (filterStatus > 0) { + ctx.logger.warn('checkIpFilter error: url = %s', callback); //todo add new error type oCallbackUrl = null; } } } - if (null === oCallbackUrl) { - return commonDefines.c_oAscServerCommandErrors.ParseError; - } else { - yield* sendStatusDocument(docId, bChangeBase, opt_userAction, oCallbackUrl, baseUrl, opt_userData); - return commonDefines.c_oAscServerCommandErrors.NoError; + if (null !== oCallbackUrl) { + return yield sendStatusDocument(ctx, docId, bChangeBase, opt_userAction, undefined, oCallbackUrl, baseUrl, opt_userData); } + return null; } +let unlockWopiDoc = co.wrap(function*(ctx, docId, opt_userIndex) { + //wopi unlock + var getRes = yield getCallback(ctx, docId, opt_userIndex); + if (getRes && getRes.wopiParams && getRes.wopiParams.userAuth && 'view' !== getRes.wopiParams.userAuth.mode) { + let unlockRes = yield wopiClient.unlock(ctx, getRes.wopiParams); + let unlockInfo = wopiClient.getWopiUnlockMarker(getRes.wopiParams); + if (unlockInfo && unlockRes) { + yield canvasService.commandOpenStartPromise(ctx, docId, undefined, unlockInfo); + } + } +}); +function* cleanDocumentOnExit(ctx, docId, deleteChanges, opt_userIndex) { + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); -function* cleanDocumentOnExit(docId, deleteChanges) { //clean redis (redisKeyPresenceSet and redisKeyPresenceHash removed with last element) - var redisArgs = [redisClient, redisClient.del, redisKeyLocks + docId, - redisKeyMessage + docId, redisKeyChangeIndex + docId, redisKeyForceSave + docId, redisKeyLastSave + docId]; - utils.promiseRedis.apply(this, redisArgs); + yield editorData.cleanDocumentOnExit(ctx, docId); //remove changes if (deleteChanges) { - sqlBase.deleteChanges(docId, null); + yield taskResult.restoreInitialPassword(ctx, docId); + sqlBase.deleteChanges(ctx, docId, null); + //delete forgotten after successful send on callbackUrl + yield storage.deletePath(ctx, docId, tenForgottenFiles); } + yield unlockWopiDoc(ctx, docId, opt_userIndex); } -function* cleanDocumentOnExitNoChanges(docId, opt_userId) { +function* cleanDocumentOnExitNoChanges(ctx, docId, opt_userId, opt_userIndex, opt_forceClose) { var userAction = opt_userId ? new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, opt_userId) : null; - // ОтправлÑем, что вÑе ушли и нет изменений (чтобы выÑтавить ÑÑ‚Ð°Ñ‚ÑƒÑ Ð½Ð° Ñервере об окончании редактированиÑ) - yield* sendStatusDocument(docId, c_oAscChangeBase.No, userAction); - //еÑли пользователь зашел в документ, Ñоединение порвалоÑÑŒ, на Ñервере удалилаÑÑŒ вÑÑ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ, - //при воÑÑтановлении ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ userIndex ÑохранитÑÑ Ð¸ он Ñовпадет Ñ userIndex Ñледующего Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ - yield* cleanDocumentOnExit(docId, false); -} - -function* _createSaveTimer(docId, opt_userId, opt_queue, opt_noDelay) { - var updateMask = new taskResult.TaskResultData(); - updateMask.key = docId; - updateMask.status = taskResult.FileStatus.Ok; - var updateTask = new taskResult.TaskResultData(); - updateTask.status = taskResult.FileStatus.SaveVersion; - updateTask.statusInfo = utils.getMillisecondsOfHour(new Date()); - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); - if (updateIfRes.affectedRows > 0) { - if(!opt_noDelay){ - yield utils.sleep(cfgAscSaveTimeOutDelay); - } - while (true) { - if (!sqlBase.isLockCriticalSection(docId)) { - canvasService.saveFromChanges(docId, updateTask.statusInfo, null, opt_userId, opt_queue); - break; + // We send that everyone is gone and there are no changes (to set the status on the server about the end of editing) + yield sendStatusDocument(ctx, docId, c_oAscChangeBase.No, userAction, opt_userIndex, undefined, undefined, undefined, opt_forceClose); + //if the user entered the document, the connection was broken, all information was deleted on the server, + //when the connection is restored, the userIndex will be saved and it will match the userIndex of the next user + yield* cleanDocumentOnExit(ctx, docId, false, opt_userIndex); +} + +function createSaveTimer(ctx, docId, opt_userId, opt_userIndex, opt_userLcid, opt_queue, opt_noDelay, opt_initShardKey) { + return co(function*(){ + const tenAscSaveTimeOutDelay = ctx.getCfg('services.CoAuthoring.server.savetimeoutdelay', cfgAscSaveTimeOutDelay); + + var updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = docId; + updateMask.status = commonDefines.FileStatus.Ok; + var updateTask = new taskResult.TaskResultData(); + updateTask.status = commonDefines.FileStatus.SaveVersion; + updateTask.statusInfo = utils.getMillisecondsOfHour(new Date()); + var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask); + if (updateIfRes.affectedRows > 0) { + if(!opt_noDelay){ + yield utils.sleep(tenAscSaveTimeOutDelay); + } + while (true) { + if (!sqlBase.isLockCriticalSection(docId)) { + yield canvasService.saveFromChanges(ctx, docId, updateTask.statusInfo, null, opt_userId, opt_userIndex, opt_userLcid, opt_queue, opt_initShardKey); + break; + } + yield utils.sleep(c_oAscLockTimeOutDelay); } - yield utils.sleep(c_oAscLockTimeOutDelay); + } else { + //if it didn't work, it means FileStatus=SaveVersion(someone else started building) or UpdateVersion(build completed) + // in this case, nothing needs to be done + ctx.logger.debug('createSaveTimer updateIf no effect'); } - } else { - //еÑли не получилоÑÑŒ - значит FileStatus=SaveVersion(кто-то другой начал Ñборку) или UpdateVersion(Ñборка закончена) - //в Ñтом Ñлучае ничего делать не надо - logger.debug('_createSaveTimer updateIf no effect'); - } + }); +} + +function checkJwt(ctx, token, type) { + return co(function*() { + const tenTokenVerifyOptions = ctx.getCfg('services.CoAuthoring.token.verifyOptions', cfgTokenVerifyOptions); + + var res = {decoded: null, description: null, code: null, token: token}; + let secret = yield tenantManager.getTenantSecret(ctx, type); + if (undefined == secret) { + ctx.logger.warn('empty secret: token = %s', token); + } + try { + res.decoded = jwt.verify(token, secret, tenTokenVerifyOptions); + ctx.logger.debug('checkJwt success: decoded = %j', res.decoded); + } catch (err) { + ctx.logger.warn('checkJwt error: name = %s message = %s token = %s', err.name, err.message, token); + if ('TokenExpiredError' === err.name) { + res.code = constants.JWT_EXPIRED_CODE; + res.description = constants.JWT_EXPIRED_REASON + err.message; + } else if ('JsonWebTokenError' === err.name) { + res.code = constants.JWT_ERROR_CODE; + res.description = constants.JWT_ERROR_REASON + err.message; + } + } + return res; + }); +} +function checkJwtHeader(ctx, req, opt_header, opt_prefix, opt_secretType) { + return co(function*() { + const tenTokenInboxHeader = ctx.getCfg('services.CoAuthoring.token.inbox.header', cfgTokenInboxHeader); + const tenTokenInboxPrefix = ctx.getCfg('services.CoAuthoring.token.inbox.prefix', cfgTokenInboxPrefix); + + let header = opt_header || tenTokenInboxHeader; + let prefix = opt_prefix || tenTokenInboxPrefix; + let secretType = opt_secretType || commonDefines.c_oAscSecretType.Inbox; + let authorization = req.get(header); + if (authorization && authorization.startsWith(prefix)) { + var token = authorization.substring(prefix.length); + return yield checkJwt(ctx, token, secretType); + } + return null; + }); +} +function getRequestParams(ctx, req, opt_isNotInBody) { + return co(function*(){ + const tenTokenEnableRequestInbox = ctx.getCfg('services.CoAuthoring.token.enable.request.inbox', cfgTokenEnableRequestInbox); + const tenTokenRequiredParams = ctx.getCfg('services.CoAuthoring.server.tokenRequiredParams', cfgTokenRequiredParams); + + let res = {code: constants.NO_ERROR, description: "", isDecoded: false, params: undefined}; + if (req.body && Buffer.isBuffer(req.body) && req.body.length > 0) { + try { + res.params = JSON.parse(req.body.toString('utf8')); + } catch(err) { + ctx.logger.debug('getRequestParams error parsing json body: %s', err.stack); + } + } + if (!res.params) { + res.params = req.query; + } + if (tenTokenEnableRequestInbox) { + res.code = constants.VKEY; + let checkJwtRes; + if (res.params.token) { + checkJwtRes = yield checkJwt(ctx, res.params.token, commonDefines.c_oAscSecretType.Inbox); + } else { + checkJwtRes = yield checkJwtHeader(ctx, req); + } + if (checkJwtRes) { + if (checkJwtRes.decoded) { + res.code = constants.NO_ERROR; + res.isDecoded = true; + if (tenTokenRequiredParams) { + res.params = {}; + } + Object.assign(res.params, checkJwtRes.decoded); + if (!utils.isEmptyObject(checkJwtRes.decoded.payload)) { + Object.assign(res.params, checkJwtRes.decoded.payload); + } + if (!utils.isEmptyObject(checkJwtRes.decoded.query)) { + Object.assign(res.params, checkJwtRes.decoded.query); + } + } else if (constants.JWT_EXPIRED_CODE == checkJwtRes.code) { + res.code = constants.VKEY_KEY_EXPIRE; + } + res.description = checkJwtRes.description; + } + } + return res; + }); } -function checkJwt(docId, token, isSession) { - var res = {decoded: null, description: null, code: null, token: token}; - var secret; - if (isSession) { - secret = utils.getSecretByElem(cfgSecretSession); +function getLicenseNowUtc() { + const now = new Date(); + return Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate(), now.getUTCHours(), + now.getUTCMinutes(), now.getUTCSeconds()) / 1000; +} +let getParticipantMap = co.wrap(function*(ctx, docId, opt_hvals) { + const participantsMap = []; + let hvals; + if (opt_hvals) { + hvals = opt_hvals; } else { - secret = utils.getSecret(docId, null, token); - } - if (undefined == secret) { - logger.error('empty secret: docId = %s token = %s', docId, token); + hvals = yield editorData.getPresence(ctx, docId, connections); } - try { - res.decoded = jwt.verify(token, secret); - logger.debug('checkJwt success: docId = %s decoded = %j', docId, res.decoded); - } catch (err) { - logger.warn('checkJwt error: docId = %s name = %s message = %s token = %s', docId, err.name, err.message, token); - if ('TokenExpiredError' === err.name) { - res.code = constants.JWT_EXPIRED_CODE; - res.description = constants.JWT_EXPIRED_REASON + err.message; - } else if ('JsonWebTokenError' === err.name) { - res.code = constants.JWT_ERROR_CODE; - res.description = constants.JWT_ERROR_REASON + err.message; + for (let i = 0; i < hvals.length; ++i) { + const elem = JSON.parse(hvals[i]); + if (!elem.isCloseCoAuthoring) { + participantsMap.push(elem); } } + return participantsMap; +}); + +function getOpenFormatByEditor(editorType) { + let res; + switch (editorType) { + case EditorTypes.spreadsheet: + res = constants.AVS_OFFICESTUDIO_FILE_CANVAS_SPREADSHEET; + break; + case EditorTypes.presentation: + res = constants.AVS_OFFICESTUDIO_FILE_CANVAS_PRESENTATION; + break; + case EditorTypes.diagram: + res = constants.AVS_OFFICESTUDIO_FILE_DRAW_VSDX; + break; + default: + res = constants.AVS_OFFICESTUDIO_FILE_CANVAS_WORD; + break; + } return res; } -function checkJwtHeader(docId, req) { - var authorization = req.get(cfgTokenInboxHeader); - if (authorization && authorization.startsWith(cfgTokenInboxPrefix)) { - var token = authorization.substring(cfgTokenInboxPrefix.length); - return checkJwt(docId, token, false); + +async function isSchemaCompatible([tableName, tableSchema]) { + const resultSchema = await sqlBase.getTableColumns(operationContext.global, tableName); + + if (resultSchema.length === 0) { + operationContext.global.logger.error('DB table "%s" does not exist', tableName); + return false; } - return null; -} -function checkJwtPayloadHash(docId, hash, body, token) { - var res = false; - if (body && Buffer.isBuffer(body)) { - var decoded = jwt.decode(token, {complete: true}); - var hmac = jwa(decoded.header.alg); - var secret = utils.getSecret(docId, null, token); - var signature = hmac.sign(body, secret); - res = (hash === signature); + + const columnArray = resultSchema.map(row => row['column_name']); + const hashedResult = new Set(columnArray); + const schemaDiff = tableSchema.filter(column => !hashedResult.has(column)); + + if (schemaDiff.length > 0) { + operationContext.global.logger.error(`DB table "${tableName}" does not contain columns: ${schemaDiff}, columns info: ${columnArray}`); + return false; } - return res; + + return true; } exports.c_oAscServerStatus = c_oAscServerStatus; +exports.editorData = editorData; +exports.editorStat = editorStat; exports.sendData = sendData; +exports.modifyConnectionForPassword = modifyConnectionForPassword; exports.parseUrl = parseUrl; exports.parseReplyData = parseReplyData; exports.sendServerRequest = sendServerRequest; -exports.createSaveTimerPromise = co.wrap(_createSaveTimer); -exports.getAllPresencePromise = co.wrap(getAllPresence); +exports.createSaveTimer = createSaveTimer; +exports.changeConnectionInfo = changeConnectionInfo; +exports.signToken = signToken; exports.publish = publish; exports.addTask = addTask; +exports.addDelayed = addDelayed; exports.removeResponse = removeResponse; exports.hasEditors = hasEditors; +exports.getEditorsCountPromise = co.wrap(getEditorsCount); exports.getCallback = getCallback; exports.getIsShutdown = getIsShutdown; -exports.getChangesIndexPromise = co.wrap(getChangesIndex); +exports.hasChanges = hasChanges; exports.cleanDocumentOnExitPromise = co.wrap(cleanDocumentOnExit); exports.cleanDocumentOnExitNoChangesPromise = co.wrap(cleanDocumentOnExitNoChanges); +exports.unlockWopiDoc = unlockWopiDoc; exports.setForceSave = setForceSave; -exports.getLastSave = getLastSave; -exports.getLastForceSave = getLastForceSave; -exports.startForceSavePromise = co.wrap(startForceSave); +exports.startForceSave = startForceSave; +exports.resetForceSaveAfterChanges = resetForceSaveAfterChanges; +exports.getExternalChangeInfo = getExternalChangeInfo; exports.checkJwt = checkJwt; +exports.getRequestParams = getRequestParams; exports.checkJwtHeader = checkJwtHeader; -exports.checkJwtPayloadHash = checkJwtPayloadHash; -exports.install = function(server, callbackFunction) { - var sockjs_opts = {sockjs_url: cfgSockjsUrl}, - sockjs_echo = sockjs.createServer(sockjs_opts), - urlParse = new RegExp("^/doc/([" + constants.DOC_ID_PATTERN + "]*)/c.+", 'i'); - sockjs_echo.on('connection', function(conn) { - if (null == conn) { - logger.error("null == conn"); - return; +async function encryptPasswordParams(ctx, data) { + let dataWithPassword; + if (data.type === 'openDocument' && data.message) { + dataWithPassword = data.message; + } else if (data.type === 'auth' && data.openCmd) { + dataWithPassword = data.openCmd; + } + if (dataWithPassword && dataWithPassword.password) { + if (dataWithPassword.password.length > constants.PASSWORD_MAX_LENGTH) { + //todo send back error + ctx.logger.warn('encryptPasswordParams password too long actual = %s; max = %s', dataWithPassword.password.length, constants.PASSWORD_MAX_LENGTH); + dataWithPassword.password = null; + } else { + dataWithPassword.password = await utils.encryptPassword(ctx, dataWithPassword.password); } - if (getIsShutdown()) { - sendFileError(conn, 'Server shutdow'); - return; + } + if (dataWithPassword && dataWithPassword.savepassword) { + if (dataWithPassword.savepassword.length > constants.PASSWORD_MAX_LENGTH) { + //todo send back error + ctx.logger.warn('encryptPasswordParams password too long actual = %s; max = %s', dataWithPassword.savepassword.length, constants.PASSWORD_MAX_LENGTH); + dataWithPassword.savepassword = null; + } else { + dataWithPassword.savepassword = await utils.encryptPassword(ctx, dataWithPassword.savepassword); } - conn.baseUrl = utils.getBaseUrlByConnection(conn); - conn.sessionIsSendWarning = false; - conn.sessionTimeConnect = conn.sessionTimeLastAction = new Date().getTime(); + } +} +exports.encryptPasswordParams = encryptPasswordParams; +exports.getOpenFormatByEditor = getOpenFormatByEditor; +exports.install = function(server, callbackFunction) { + const io = new Server(server, cfgSocketIoConnection); - conn.on('data', function(message) { - return co(function* () { - var docId = 'null'; + io.use((socket, next) => { + co(function*(){ + let ctx = new operationContext.Context(); + let res; + let checkJwtRes; try { - var startDate = null; - if(clientStatsD) { - startDate = new Date(); - } - var data = JSON.parse(message); - docId = conn.docId; - logger.info('data.type = ' + data.type + ' id = ' + docId); - if(getIsShutdown()) - { - logger.debug('Server shutdown receive data'); - return; - } - if (conn.isCiriticalError && ('message' == data.type || 'getLock' == data.type || 'saveChanges' == data.type || - 'isSaveLock' == data.type)) { - logger.warn("conn.isCiriticalError send command: docId = %s type = %s", docId, data.type); - conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); - return; - } - if ((conn.isCloseCoAuthoring || (conn.user && conn.user.view)) && - ('getLock' == data.type || 'saveChanges' == data.type || 'isSaveLock' == data.type)) { - logger.warn("conn.user.view||isCloseCoAuthoring access deny: docId = %s type = %s", docId, data.type); - conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); - return; + ctx.initFromConnection(socket); + yield ctx.initTenantCache(); + ctx.logger.info('io.use start'); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + + let handshake = socket.handshake; + if (tenTokenEnableBrowser) { + let secretType = !!(handshake?.auth?.session) ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser; + let token = handshake?.auth?.session || handshake?.auth?.token; + checkJwtRes = yield checkJwt(ctx, token, secretType); + if (!checkJwtRes.decoded) { + res = new Error("not authorized"); + res.data = { code: checkJwtRes.code, description: checkJwtRes.description }; + } } - switch (data.type) { - case 'auth' : - yield* auth(conn, data); - break; - case 'message' : - yield* onMessage(conn, data); - break; - case 'cursor' : - yield* onCursor(conn, data); - break; - case 'getLock' : - yield* getLock(conn, data, false); - break; - case 'saveChanges' : - yield* saveChanges(conn, data); - break; - case 'isSaveLock' : - yield* isSaveLock(conn, data); - break; - case 'unSaveLock' : - yield* unSaveLock(conn, -1, -1); - break; // Ð˜Ð½Ð´ÐµÐºÑ Ð¾Ñ‚Ð¿Ñ€Ð°Ð²Ð»Ñем -1, Ñ‚.к. Ñто ÑкÑтренное ÑнÑтие без ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - case 'getMessages' : - yield* getMessages(conn, data); - break; - case 'unLockDocument' : - yield* checkEndAuthLock(data.unlock, data.isSave, docId, conn.user.id, conn); - break; - case 'close': - yield* closeDocument(conn, false); - break; - case 'versionHistory' : - yield* versionHistory(conn, new commonDefines.InputCommand(data.cmd)); - break; - case 'openDocument' : - var cmd = new commonDefines.InputCommand(data.message); - yield canvasService.openDocument(conn, cmd); - break; - case 'changesError': - logger.error("changesError: docId = %s %s", docId, data.stack); - break; - case 'extendSession' : - conn.sessionIsSendWarning = false; - conn.sessionTimeLastAction = new Date().getTime() - data.idletime; - break; - case 'refreshToken' : - var isSession = !!data.jwtSession; - var checkJwtRes = checkJwt(docId, data.jwtSession || data.jwtOpen, isSession); - if (checkJwtRes.decoded) { - if (checkJwtRes.decoded.document.key == conn.docId) { - sendDataRefreshToken(conn, {token: fillJwtByConnection(conn), expires: cfgTokenSessionExpires}); - } else { - conn.close(constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); + } catch (err) { + ctx.logger.error('io.use error: %s', err.stack); + } finally { + ctx.logger.info('io.use end'); + next(res); + } + }); + }); + + io.on('connection', async function(conn) { + let ctx = new operationContext.Context(); + try { + if (!conn) { + operationContext.global.logger.error("null == conn"); + return; + } + ctx.initFromConnection(conn); + await ctx.initTenantCache(); + if (constants.DEFAULT_DOC_ID === ctx.docId) { + ctx.logger.error('io.on connection unexpected key use key pattern = "%s" url = %s', constants.DOC_ID_PATTERN, conn.handshake?.url); + sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); + conn.disconnect(true); + return; + } + if (getIsShutdown()) { + sendDataDisconnectReason(ctx, conn, constants.SHUTDOWN_CODE, constants.SHUTDOWN_REASON); + conn.disconnect(true); + return; + } + conn.baseUrl = utils.getBaseUrlByConnection(ctx, conn); + conn.sessionIsSendWarning = false; + conn.sessionTimeConnect = conn.sessionTimeLastAction = new Date().getTime(); + + conn.on('message', function(data) { + return co(function* () { + var docId = 'null'; + let ctx = new operationContext.Context(); + try { + ctx.initFromConnection(conn); + yield ctx.initTenantCache(); + const tenErrorFiles = ctx.getCfg('FileConverter.converter.errorfiles', cfgErrorFiles); + + var startDate = null; + if(clientStatsD) { + startDate = new Date(); + } + + docId = conn.docId; + ctx.logger.info('data.type = %s', data.type); + if(getIsShutdown()) + { + ctx.logger.debug('Server shutdown receive data'); + return; + } + if ((conn.isCloseCoAuthoring || (conn.user && conn.user.view)) && + ('getLock' == data.type || 'saveChanges' == data.type || 'isSaveLock' == data.type)) { + ctx.logger.warn("conn.user.view||isCloseCoAuthoring access deny: type = %s", data.type); + sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); + conn.disconnect(true); + return; + } + yield encryptPasswordParams(ctx, data); + switch (data.type) { + case 'auth' : + try { + yield* auth(ctx, conn, data); + } catch(err){ + ctx.logger.error('auth error: %s', err.stack); + sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); + conn.disconnect(true); + return; + } + break; + case 'message' : + yield* onMessage(ctx, conn, data); + break; + case 'cursor' : + yield* onCursor(ctx, conn, data); + break; + case 'getLock' : + yield getLock(ctx, conn, data, false); + break; + case 'saveChanges' : + yield* saveChanges(ctx, conn, data); + break; + case 'isSaveLock' : + yield* isSaveLock(ctx, conn, data); + break; + case 'unSaveLock' : + yield* unSaveLock(ctx, conn, -1, -1, -1); + break; // The index is sent -1, because this is an emergency withdrawal without saving + case 'getMessages' : + yield* getMessages(ctx, conn, data); + break; + case 'unLockDocument' : + yield* checkEndAuthLock(ctx, data.unlock, data.isSave, docId, conn.user.id, data.releaseLocks, data.deleteIndex, conn); + break; + case 'close': + yield* closeDocument(ctx, conn); + break; + case 'openDocument' : { + var cmd = new commonDefines.InputCommand(data.message); + cmd.fillFromConnection(conn); + yield canvasService.openDocument(ctx, conn, cmd); + break; } - } else { - conn.close(checkJwtRes.code, checkJwtRes.description); + case 'clientLog': + let level = data.level?.toLowerCase(); + if("trace" === level || "debug" === level || "info" === level || "warn" === level || "error" === level || "fatal" === level) { + ctx.logger[level]("clientLog: %s", data.msg); + } + if ("error" === level && tenErrorFiles && docId) { + let destDir = 'browser/' + docId; + yield storage.copyPath(ctx, docId, destDir, undefined, tenErrorFiles); + yield* saveErrorChanges(ctx, docId, destDir); + } + break; + case 'extendSession' : + ctx.logger.debug("extendSession idletime: %d", data.idletime); + conn.sessionIsSendWarning = false; + conn.sessionTimeLastAction = new Date().getTime() - data.idletime; + break; + case 'forceSaveStart' : + var forceSaveRes; + if (conn.user) { + forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Button, + undefined, undefined, conn.user.idOriginal, conn.user.id, + undefined, conn.user.indexUser, undefined, undefined, undefined, undefined, conn); + } else { + forceSaveRes = {code: commonDefines.c_oAscServerCommandErrors.UnknownError, time: null}; + } + sendData(ctx, conn, {type: "forceSaveStart", messages: forceSaveRes}); + break; + case 'rpc' : + yield* startRPC(ctx, conn, data.responseKey, data.data); + break; + case 'authChangesAck' : + delete conn.authChangesAck; + break; + default: + ctx.logger.debug("unknown command %j", data); + break; } - break; - case 'forceSaveStart' : - var forceSaveRes; - if (conn.user) { - forceSaveRes = yield* startForceSave(docId, commonDefines.c_oAscForceSaveTypes.Button, undefined, conn.user.id); - } else { - forceSaveRes = {code: commonDefines.c_oAscServerCommandErrors.UnknownError, time: null}; + + if (clientStatsD) { + let isSendMetric = 'auth' === data.type || 'getLock' === data.type || 'saveChanges' === data.type; + if (isSendMetric) { + clientStatsD.timing('coauth.data.' + data.type, new Date() - startDate); + } } - sendData(conn, {type: "forceSaveStart", messages: forceSaveRes}); - break; - default: - logger.debug("unknown command %s", message); - break; - } - if(clientStatsD) { - if('openDocument' != data.type) { - clientStatsD.timing('coauth.data.' + data.type, new Date() - startDate); + } catch (e) { + ctx.logger.error("error receiving response: type = %s %s", (data && data.type) ? data.type : 'null', e.stack); } - } - } catch (e) { - logger.error("error receiving response: docId = %s type = %s\r\n%s", docId, (data && data.type) ? data.type : 'null', e.stack); - } + }); }); - }); - conn.on('error', function() { - logger.error("On error"); - }); - conn.on('close', function() { - return co(function* () { - var docId = 'null'; - try { - docId = conn.docId; - yield* closeDocument(conn, true); - } catch (err) { - logger.error('Error conn close: docId = %s\r\n%s', docId, err.stack); - } + conn.on("disconnect", function(reason) { + return co(function* () { + let ctx = new operationContext.Context(); + try { + ctx.initFromConnection(conn); + yield ctx.initTenantCache(); + yield* closeDocument(ctx, conn, reason); + } catch (err) { + ctx.logger.error('Error conn close: %s', err.stack); + } + }); }); - }); - _checkLicense(conn); + _checkLicense(ctx, conn); + } catch(err){ + ctx.logger.error('connection error: %s', err.stack); + sendDataDisconnectReason(ctx, conn, constants.DROP_CODE, constants.DROP_REASON); + conn.disconnect(true); + } + }); + io.engine.on("connection_error", (err) => { + operationContext.global.logger.warn('io.connection_error code=%s, message=%s', err.code, err.message); }); /** * + * @param ctx * @param conn - * @param isCloseConnection - закрываем ли мы окончательно Ñоединение + * @param reason - the reason of the disconnection (either client or server-side) */ - function* closeDocument(conn, isCloseConnection) { + function* closeDocument(ctx, conn, reason) { + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + + ctx.logger.info("Connection closed or timed out: reason = %s", reason); var userLocks, reconnected = false, bHasEditors, bHasChanges; var docId = conn.docId; if (null == docId) { return; } var hvals; + let participantsTimestamp; var tmpUser = conn.user; var isView = tmpUser.view; - logger.info("Connection closed or timed out: userId = %s isCloseConnection = %s docId = %s", tmpUser.id, isCloseConnection, docId); + var isCloseCoAuthoringTmp = conn.isCloseCoAuthoring; - if (isCloseConnection) { + if (reason) { //Notify that participant has gone connections = _.reject(connections, function(el) { return el.id === conn.id;//Delete this connection }); //Check if it's not already reconnected - reconnected = yield* isUserReconnect(docId, tmpUser.id, conn.id); + reconnected = yield* isUserReconnect(ctx, docId, tmpUser.id, conn.id); if (reconnected) { - logger.info("reconnected: userId = %s docId = %s", tmpUser.id, docId); + ctx.logger.info("reconnected"); } else { - var multi = redisClient.multi([['hdel', redisKeyPresenceHash + docId, tmpUser.id], - ['zrem', redisKeyPresenceSet + docId, tmpUser.id]]); - yield utils.promiseRedis(multi, multi.exec); - hvals = yield* getAllPresence(docId); + yield removePresence(ctx, conn); + hvals = yield editorData.getPresence(ctx, docId, connections); + participantsTimestamp = Date.now(); if (hvals.length <= 0) { - yield utils.promiseRedis(redisClient, redisClient.zrem, redisKeyDocuments, docId); + yield editorData.removePresenceDocument(ctx, docId); } } } else { - if (!conn.isCloseCoAuthoring) { - tmpUser.view = true; + if (!conn.isCloseCoAuthoring && !isView) { + modifyConnectionEditorToView(ctx, conn); conn.isCloseCoAuthoring = true; - yield* updatePresence(docId, tmpUser.id, getConnectionInfo(conn)); - if (cfgTokenEnableBrowser) { - sendDataRefreshToken(conn, {token: fillJwtByConnection(conn), expires: cfgTokenSessionExpires}); + yield addPresence(ctx, conn, true); + if (tenTokenEnableBrowser) { + let sessionToken = yield fillJwtByConnection(ctx, conn); + sendDataRefreshToken(ctx, conn, sessionToken); } } } @@ -1239,102 +1867,88 @@ exports.install = function(server, callbackFunction) { //revert old view to send event var tmpView = tmpUser.view; tmpUser.view = isView; - yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: docId, user: tmpUser, state: false}, docId, tmpUser.id); - tmpUser.view = tmpView; - - // Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñнимаем лок Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - var saveLock = yield utils.promiseRedis(redisClient, redisClient.get, redisKeySaveLock + docId); - if (conn.user.id == saveLock) { - yield utils.promiseRedis(redisClient, redisClient.del, redisKeySaveLock + docId); + let participants = yield getParticipantMap(ctx, docId, hvals); + if (!participantsTimestamp) { + participantsTimestamp = Date.now(); } + yield publish(ctx, {type: commonDefines.c_oPublishType.participantsState, ctx: ctx, docId: docId, userId: tmpUser.id, participantsTimestamp: participantsTimestamp, participants: participants}, docId, tmpUser.id); + tmpUser.view = tmpView; - // Только еÑли редактируем + // editors only if (false === isView) { - bHasEditors = yield* hasEditors(docId, hvals); - var puckerIndex = yield* getChangesIndex(docId); - bHasChanges = puckerIndex > 0; - - // ЕÑли у Ð½Ð°Ñ Ð½ÐµÑ‚ пользователей, то удалÑем вÑе ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ - if (!bHasEditors) { - // Ðа вÑÑкий Ñлучай Ñнимаем lock - yield utils.promiseRedis(redisClient, redisClient.del, redisKeySaveLock + docId); - - // Send changes to save server - if (bHasChanges) { - yield* _createSaveTimer(docId, tmpUser.idOriginal); - } else { - yield* cleanDocumentOnExitNoChanges(docId, tmpUser.idOriginal); + // For this user, we remove the lock from saving + yield editorData.unlockSave(ctx, docId, conn.user.id); + + bHasEditors = yield* hasEditors(ctx, docId, hvals); + bHasChanges = yield hasChanges(ctx, docId); + + let needSendStatus = true; + if (conn.encrypted) { + let selectRes = yield taskResult.select(ctx, docId); + if (selectRes.length > 0) { + var row = selectRes[0]; + if (commonDefines.FileStatus.UpdateVersion === row.status) { + needSendStatus = false; + } } - } else { - yield* sendStatusDocument(docId, c_oAscChangeBase.No, new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, tmpUser.idOriginal)); } - - //ДавайдоÑвиданьÑ! //Release locks - userLocks = yield* getUserLocks(docId, conn.sessionId); + userLocks = yield removeUserLocks(ctx, docId, conn.user.id); if (0 < userLocks.length) { - //todo на close Ñебе ничего не шлем + //todo send nothing in case of close document //sendReleaseLock(conn, userLocks); - yield* publish({type: commonDefines.c_oPublishType.releaseLock, docId: docId, userId: conn.user.id, locks: userLocks}, docId, conn.user.id); + yield publish(ctx, {type: commonDefines.c_oPublishType.releaseLock, ctx: ctx, docId: docId, userId: conn.user.id, locks: userLocks}, docId, conn.user.id); } - // Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñнимаем Lock Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð° - yield* checkEndAuthLock(true, false, docId, conn.user.id); - } - } - } + // For this user, remove the Lock from the document + yield* checkEndAuthLock(ctx, true, false, docId, conn.user.id); - function* versionHistory(conn, cmd) { - var docIdOld = conn.docId; - var docIdNew = cmd.getDocId(); - //check jwt - if (cfgTokenEnableBrowser) { - var checkJwtRes = checkJwt(docIdNew, cmd.getJwt(), false); - if (checkJwtRes.decoded) { - fillVersionHistoryFromJwt(checkJwtRes.decoded, cmd); - docIdNew = cmd.getDocId(); - } else { - if (constants.JWT_EXPIRED_CODE == checkJwtRes.code && !cmd.getCloseOnError()) { - sendData(conn, {type: "expiredToken"}); - } else { - conn.close(checkJwtRes.code, checkJwtRes.description); + let userIndex = utils.getIndexFromUserId(tmpUser.id, tmpUser.idOriginal); + // If we do not have users, then delete all messages + if (!bHasEditors) { + // Just in case, remove the lock + yield editorData.unlockSave(ctx, docId, tmpUser.id); + + let needSaveChanges = bHasChanges; + if (!needSaveChanges) { + //start save changes if forgotten file exists. + //more effective to send file without sfc, but this method is simpler by code + let forgotten = yield storage.listObjects(ctx, docId, tenForgottenFiles); + needSaveChanges = forgotten.length > 0; + ctx.logger.debug('closeDocument hasForgotten %s', needSaveChanges); + } + if (needSaveChanges && !conn.encrypted) { + // Send changes to save server + let user_lcid = utilsDocService.localeToLCID(conn.lang); + yield createSaveTimer(ctx, docId, tmpUser.idOriginal, userIndex, user_lcid, undefined, getIsShutdown()); + } else if (needSendStatus) { + yield* cleanDocumentOnExitNoChanges(ctx, docId, tmpUser.idOriginal, userIndex); + } else { + yield* cleanDocumentOnExit(ctx, docId, false, userIndex); + } + } else if (needSendStatus) { + yield sendStatusDocument(ctx, docId, c_oAscChangeBase.No, new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, tmpUser.idOriginal), userIndex); } - return; } - } - if (docIdOld !== docIdNew) { - var tmpUser = conn.user; - //remove presence(other data was removed before in closeDocument) - var multi = redisClient.multi([ - ['hdel', redisKeyPresenceHash + docIdOld, tmpUser.id], - ['zrem', redisKeyPresenceSet + docIdOld, tmpUser.id] - ]); - yield utils.promiseRedis(multi, multi.exec); - var hvals = yield* getAllPresence(docIdOld); - if (hvals.length <= 0) { - yield utils.promiseRedis(redisClient, redisClient.zrem, redisKeyDocuments, docIdOld); - } - - //apply new - conn.docId = docIdNew; - yield* updatePresence(docIdNew, tmpUser.id, getConnectionInfo(conn)); - if (cfgTokenEnableBrowser) { - sendDataRefreshToken(conn, {token: fillJwtByConnection(conn), expires: cfgTokenSessionExpires}); + let sessionType = isView ? 'view' : 'edit'; + let sessionTimeMs = new Date().getTime() - conn.sessionTimeConnect; + ctx.logger.debug(`closeDocument %s session time:%s`, sessionType, sessionTimeMs); + if(clientStatsD) { + clientStatsD.timing(`coauth.session.${sessionType}`, sessionTimeMs); } } - //open - yield canvasService.openDocument(conn, cmd, null); } - // Получение изменений Ð´Ð»Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð° (либо из кÑша, либо обращаемÑÑ Ðº базе, но только еÑли были ÑохранениÑ) - function* getDocumentChanges(docId, optStartIndex, optEndIndex) { - // ЕÑли за тот момент, пока мы ждали из базы ответа, вÑе ушли, то отправлÑть ничего не нужно - var arrayElements = yield sqlBase.getChangesPromise(docId, optStartIndex, optEndIndex); + + // Getting changes for the document (either from the cache or accessing the database, but only if there were saves) + function* getDocumentChanges(ctx, docId, optStartIndex, optEndIndex) { + // If during that moment, while we were waiting for a response from the database, everyone left, then nothing needs to be sent + var arrayElements = yield sqlBase.getChangesPromise(ctx, docId, optStartIndex, optEndIndex); var j, element; var objChangesDocument = new DocumentChanges(docId); for (j = 0; j < arrayElements.length; ++j) { element = arrayElements[j]; - // ДобавлÑем GMT, Ñ‚.к. в базу данных мы пишем UTC, но ÑохранÑетÑÑ Ñ‚ÑƒÐ´Ð° Ñтрока без UTC и при зачитывании будет неправильное Ð²Ñ€ÐµÐ¼Ñ + // We add GMT, because. we write UTC to the database, but the string without UTC is saved there and the time will be wrong when reading objChangesDocument.push({docid: docId, change: element['change_data'], time: element['change_date'].getTime(), user: element['user_id'], useridoriginal: element['user_id_original']}); @@ -1342,124 +1956,157 @@ exports.install = function(server, callbackFunction) { return objChangesDocument; } - function* getAllLocks(docId) { - var docLockRes = []; - var docLock = yield utils.promiseRedis(redisClient, redisClient.lrange, redisKeyLocks + docId, 0, -1); - for (var i = 0; i < docLock.length; ++i) { - docLockRes.push(JSON.parse(docLock[i])); - } - return docLockRes; - } - function* addLocks(docId, toCache, isReplace) { - if (toCache && toCache.length > 0) { - toCache.unshift('rpush', redisKeyLocks + docId); - var multiArgs = [toCache, ['expire', redisKeyLocks + docId, cfgExpLocks]]; - if (isReplace) { - multiArgs.unshift(['del', redisKeyLocks + docId]); - } - var multi = redisClient.multi(multiArgs); - yield utils.promiseRedis(multi, multi.exec); - } - } - function* getUserLocks(docId, sessionId) { - var userLocks = [], i; - var toCache = []; - var docLock = yield* getAllLocks(docId); - for (i = 0; i < docLock.length; ++i) { - var elem = docLock[i]; - if (elem.sessionId === sessionId) { - userLocks.push(elem); - } else { - toCache.push(JSON.stringify(elem)); + async function removeUserLocks(ctx, docId, userId) { + let locks = await editorData.getLocks(ctx, docId); + let res = []; + let toRemove = {}; + for (let lockId in locks) { + let lock = locks[lockId]; + if (lock.user === userId) { + toRemove[lockId] = lock; + res.push(lock); } } - //remove all - yield utils.promiseRedis(redisClient, redisClient.del, redisKeyLocks + docId); - //set all - yield* addLocks(docId, toCache); - return userLocks; + await editorData.removeLocks(ctx, docId, toRemove); + return res; } - function* getParticipantMap(docId, opt_userId, opt_connInfo) { - var participantsMap = []; - var hvals = yield* getAllPresence(docId, opt_userId, opt_connInfo); - for (var i = 0; i < hvals.length; ++i) { - var elem = JSON.parse(hvals[i]); - if (!elem.isCloseCoAuthoring) { - participantsMap.push(elem); + function* checkEndAuthLock(ctx, unlock, isSave, docId, userId, releaseLocks, deleteIndex, conn) { + let result = false; + + if (null != deleteIndex && -1 !== deleteIndex) { + let puckerIndex = yield* getChangesIndex(ctx, docId); + const deleteCount = puckerIndex - deleteIndex; + if (0 < deleteCount) { + puckerIndex -= deleteCount; + yield sqlBase.deleteChangesPromise(ctx, docId, deleteIndex); + } else if (0 > deleteCount) { + ctx.logger.error("Error checkEndAuthLock: deleteIndex: %s ; startIndex: %s ; deleteCount: %s", + deleteIndex, puckerIndex, deleteCount); } } - return participantsMap; - } - function* checkEndAuthLock(unlock, isSave, docId, userId, currentConnection) { - let result = false; if (unlock) { - const lockDocument = yield utils.promiseRedis(redisClient, redisClient.get, redisKeyLockDoc + docId); - if (lockDocument && userId === JSON.parse(lockDocument).id) { - yield utils.promiseRedis(redisClient, redisClient.del, redisKeyLockDoc + docId); - - const participantsMap = yield* getParticipantMap(docId); - yield* publish({ + var unlockRes = yield editorData.unlockAuth(ctx, docId, userId); + if (commonDefines.c_oAscUnlockRes.Unlocked === unlockRes) { + const participantsMap = yield getParticipantMap(ctx, docId); + yield publish(ctx, { type: commonDefines.c_oPublishType.auth, + ctx: ctx, docId: docId, userId: userId, participantsMap: participantsMap - }, docId, userId); + }); result = true; } } //Release locks - if (isSave) { - const userLocks = yield* getUserLocks(docId, currentConnection.sessionId); + if (releaseLocks && conn) { + const userLocks = yield removeUserLocks(ctx, docId, userId); if (0 < userLocks.length) { - sendReleaseLock(currentConnection, userLocks); - yield* publish( - {type: commonDefines.c_oPublishType.releaseLock, docId: docId, userId: userId, locks: userLocks}, - docId, userId); + sendReleaseLock(ctx, conn, userLocks); + yield publish(ctx, { + type: commonDefines.c_oPublishType.releaseLock, + ctx: ctx, + docId: docId, + userId: userId, + locks: userLocks + }, docId, userId); } - - // ÐвтоматичеÑки Ñнимаем lock Ñами - yield* unSaveLock(currentConnection, -1, -1); + } + if (isSave && conn) { + // Automatically remove the lock ourselves + yield* unSaveLock(ctx, conn, -1, -1, -1); } return result; } - function sendParticipantsState(participants, data) { + function* setLockDocumentTimer(ctx, docId, userId) { + const tenExpLockDoc = ctx.getCfg('services.CoAuthoring.expire.lockDoc', cfgExpLockDoc); + let timerId = setTimeout(function() { + return co(function*() { + try { + ctx.logger.warn("lockDocumentsTimerId timeout"); + delete lockDocumentsTimerId[docId]; + //todo remove checkEndAuthLock(only needed for lost connections in redis) + yield* checkEndAuthLock(ctx, true, false, docId, userId); + yield* publishCloseUsersConnection(ctx, docId, [userId], false, constants.DROP_CODE, constants.DROP_REASON); + } catch (e) { + ctx.logger.error("lockDocumentsTimerId error: %s", e.stack); + } + }); + }, 1000 * tenExpLockDoc); + lockDocumentsTimerId[docId] = {timerId: timerId, userId: userId}; + ctx.logger.debug("lockDocumentsTimerId set"); + } + function cleanLockDocumentTimer(docId, lockDocumentTimer) { + clearTimeout(lockDocumentTimer.timerId); + delete lockDocumentsTimerId[docId]; + } + + function sendParticipantsState(ctx, participants, data) { _.each(participants, function(participant) { - sendData(participant, { + sendData(ctx, participant, { type: "connectState", - state: data.state, - user: data.user + participantsTimestamp: data.participantsTimestamp, + participants: data.participants, + waitAuth: !!data.waitAuthUserId }); }); } - function sendFileError(conn, errorId) { - logger.error('error description: docId = %s errorId = %s', conn.docId, errorId); - conn.isCiriticalError = true; - sendData(conn, {type: 'error', description: errorId}); + function sendFileError(ctx, conn, errorId, code, opt_notWarn) { + if (opt_notWarn) { + ctx.logger.debug('error description: errorId = %s', errorId); + } else { + ctx.logger.warn('error description: errorId = %s', errorId); + } + sendData(ctx, conn, {type: 'error', description: errorId, code: code}); + } + + function* sendFileErrorAuth(ctx, conn, sessionId, errorId, code, opt_notWarn) { + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + + conn.sessionId = sessionId;//restore old + //Kill previous connections + connections = _.reject(connections, function(el) { + return el.sessionId === sessionId;//Delete this connection + }); + //closing could happen during async action + if (constants.CONN_CLOSED !== conn.conn.readyState) { + modifyConnectionEditorToView(ctx, conn); + conn.isCloseCoAuthoring = true; + + // We put it in an array, because we need to send data to open/save the document + connections.push(conn); + yield addPresence(ctx, conn, true); + if (tenTokenEnableBrowser) { + let sessionToken = yield fillJwtByConnection(ctx, conn); + sendDataRefreshToken(ctx, conn, sessionToken); + } + sendFileError(ctx, conn, errorId, code, opt_notWarn); + } } - // ПереÑчет только Ð´Ð»Ñ Ñ‡ÑƒÐ¶Ð¸Ñ… Lock при Ñохранении на клиенте, который добавлÑл/удалÑл Ñтроки или Ñтолбцы + // Recalculation only for foreign Lock when saving on a client that added/deleted rows or columns function _recalcLockArray(userId, _locks, oRecalcIndexColumns, oRecalcIndexRows) { + let res = {}; if (null == _locks) { - return false; + return res; } - var count = _locks.length; var element = null, oRangeOrObjectId = null; - var i; var sheetId = -1; - var isModify = false; - for (i = 0; i < count; ++i) { - // Ð”Ð»Ñ Ñамого ÑÐµÐ±Ñ Ð½Ðµ переÑчитываем - if (userId === _locks[i].user) { + for (let lockId in _locks) { + let isModify = false; + let lock = _locks[lockId]; + // we do not count for ourselves + if (userId === lock.user) { continue; } - element = _locks[i].block; + element = lock.block; if (c_oAscLockTypeElem.Range !== element["type"] || c_oAscLockTypeElemSubType.InsertColumns === element["subType"] || c_oAscLockTypeElemSubType.InsertRows === element["subType"]) { @@ -1470,19 +2117,22 @@ exports.install = function(server, callbackFunction) { oRangeOrObjectId = element["rangeOrObjectId"]; if (oRecalcIndexColumns && oRecalcIndexColumns.hasOwnProperty(sheetId)) { - // ПереÑчет колонок + // Column index recalculation oRangeOrObjectId["c1"] = oRecalcIndexColumns[sheetId].getLockMe2(oRangeOrObjectId["c1"]); oRangeOrObjectId["c2"] = oRecalcIndexColumns[sheetId].getLockMe2(oRangeOrObjectId["c2"]); isModify = true; } if (oRecalcIndexRows && oRecalcIndexRows.hasOwnProperty(sheetId)) { - // ПереÑчет Ñтрок + // row index recalculation oRangeOrObjectId["r1"] = oRecalcIndexRows[sheetId].getLockMe2(oRangeOrObjectId["r1"]); oRangeOrObjectId["r2"] = oRecalcIndexRows[sheetId].getLockMe2(oRangeOrObjectId["r2"]); isModify = true; } + if (isModify) { + res[lockId] = lock; + } } - return isModify; + return res; } function _addRecalcIndex(oRecalcIndex) { @@ -1506,7 +2156,7 @@ exports.install = function(server, callbackFunction) { } nRecalcType = (c_oAscRecalcIndexTypes.RecalcIndexAdd === oRecalcIndexElement._recalcType) ? c_oAscRecalcIndexTypes.RecalcIndexRemove : c_oAscRecalcIndexTypes.RecalcIndexAdd; - // Дублируем Ð´Ð»Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‚Ð° результата (нам нужно переÑчитать только по поÑледнему индекÑу + // Duplicate to return the result (we only need to recalculate by the last index oRecalcIndexResult[sheetId].add(nRecalcType, oRecalcIndexElement._position, oRecalcIndexElement._count, /*bIsSaveIndex*/true); } @@ -1517,12 +2167,12 @@ exports.install = function(server, callbackFunction) { } function compareExcelBlock(newBlock, oldBlock) { - // Это lock Ð´Ð»Ñ ÑƒÐ´Ð°Ð»ÐµÐ½Ð¸Ñ Ð¸Ð»Ð¸ Ð´Ð¾Ð±Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ñтрок/Ñтолбцов + // This is a lock to remove or add rows/columns if (null !== newBlock.subType && null !== oldBlock.subType) { return true; } - // Ðе учитываем lock от ChangeProperties (только еÑли Ñто не lock лиÑта) + // Ignore lock from ChangeProperties (only if it's not a leaf lock) if ((c_oAscLockTypeElemSubType.ChangeProperties === oldBlock.subType && c_oAscLockTypeElem.Sheet !== newBlock.type) || (c_oAscLockTypeElemSubType.ChangeProperties === newBlock.subType && @@ -1533,7 +2183,7 @@ exports.install = function(server, callbackFunction) { var resultLock = false; if (newBlock.type === c_oAscLockTypeElem.Range) { if (oldBlock.type === c_oAscLockTypeElem.Range) { - // Ðе учитываем lock от Insert + // We do not take into account lock from Insert if (c_oAscLockTypeElemSubType.InsertRows === oldBlock.subType || c_oAscLockTypeElemSubType.InsertColumns === oldBlock.subType) { resultLock = false; } else if (isInterSection(newBlock.rangeOrObjectId, oldBlock.rangeOrObjectId)) { @@ -1561,7 +2211,6 @@ exports.install = function(server, callbackFunction) { return true; } - // Сравнение Ð´Ð»Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ð¹ function comparePresentationBlock(newBlock, oldBlock) { var resultLock = false; @@ -1591,35 +2240,111 @@ exports.install = function(server, callbackFunction) { return resultLock; } - function* authRestore(conn, sessionId) { + function* authRestore(ctx, conn, sessionId) { conn.sessionId = sessionId;//restore old //Kill previous connections connections = _.reject(connections, function(el) { return el.sessionId === sessionId;//Delete this connection }); - yield* endAuth(conn, true); + yield* endAuth(ctx, conn, true); } - function fillUsername(data) { + function fillUsername(ctx, data) { + let name; let user = data.user; if (user.firstname && user.lastname) { //as in web-apps/apps/common/main/lib/util/utils.js let isRu = (data.lang && /^ru/.test(data.lang)); - return isRu ? user.lastname + ' ' + user.firstname : user.firstname + ' ' + user.lastname; + name = isRu ? user.lastname + ' ' + user.firstname : user.firstname + ' ' + user.lastname; } else { - return user.username; + name = user.username || "Anonymous"; + } + if (name.length > constants.USER_NAME_MAX_LENGTH) { + ctx.logger.warn('fillUsername user name too long actual = %s; max = %s', name.length, constants.USER_NAME_MAX_LENGTH); + name = name.substr(0, constants.USER_NAME_MAX_LENGTH); } + return name; } - function isEditMode(permissions, mode, def) { - if (permissions && mode) { - //as in web-apps/apps/documenteditor/main/app/controller/Main.js - return (permissions.edit !== false || permissions.review === true) && mode !== 'view'; - } else { - return def; + function isEditMode(permissions, mode) { + //like this.api.asc_setViewMode(!this.appOptions.isEdit && !this.appOptions.isRestrictedEdit); + //https://github.com/ONLYOFFICE/web-apps/blob/4a7879b4f88f315fe94d9f7d97c0ed8aa9f82221/apps/documenteditor/main/app/controller/Main.js#L1743 + //todo permissions in embed editor + //https://github.com/ONLYOFFICE/web-apps/blob/72b8350c71e7b314b63b8eec675e76156bb4a2e4/apps/documenteditor/forms/app/controller/ApplicationController.js#L627 + return (!mode || mode !== 'view') && (!permissions || permissions.edit !== false || permissions.review === true || + permissions.comment === true || permissions.fillForms === true); + } + function fillDataFromWopiJwt(decoded, data) { + let res = true; + var openCmd = data.openCmd; + + if (decoded.key) { + data.docid = decoded.key; + } + if (decoded.userAuth) { + data.documentCallbackUrl = JSON.stringify(decoded.userAuth); + data.mode = decoded.userAuth.mode; + } + if (decoded.queryParams) { + let queryParams = decoded.queryParams; + data.lang = queryParams.lang || queryParams.ui || constants.TEMPLATES_DEFAULT_LOCALE; } + if (wopiClient.isWopiJwtToken(decoded)) { + let fileInfo = decoded.fileInfo; + let queryParams = decoded.queryParams; + if (openCmd) { + openCmd.format = wopiClient.getFileTypeByInfo(fileInfo); + openCmd.title = fileInfo.BreadcrumbDocName || fileInfo.BaseFileName; + } + let name = fileInfo.IsAnonymousUser ? "" : fileInfo.UserFriendlyName; + if (name) { + data.user.username = name; + data.denyChangeName = true; + } + if (null != fileInfo.UserId) { + data.user.id = fileInfo.UserId; + if (openCmd) { + openCmd.userid = fileInfo.UserId; + } + } + let permissionsEdit = !fileInfo.ReadOnly && fileInfo.UserCanWrite && queryParams?.formsubmit !== "1"; + let permissionsFillForm = permissionsEdit || queryParams?.formsubmit === "1"; + let permissions = { + edit: permissionsEdit, + review: (fileInfo.SupportsReviewing === false) ? false : (fileInfo.UserCanReview === false ? false : fileInfo.UserCanReview), + copy: fileInfo.CopyPasteRestrictions !== "CurrentDocumentOnly" && fileInfo.CopyPasteRestrictions !== "BlockAll", + print: !fileInfo.DisablePrint && !fileInfo.HidePrintOption, + chat: queryParams?.dchat!=="1", + fillForms: permissionsFillForm + }; + //todo (review: undefiend) + // res = deepEqual(data.permissions, permissions, {strict: true}); + if (!data.permissions) { + data.permissions = {}; + } + //not '=' because if it jwt from previous version, we must use values from data + Object.assign(data.permissions, permissions); + } + return res; } - function fillDataFromJwt(decoded, data) { + function validateAuthToken(data, decoded) { + var res = ""; + if (!decoded?.document?.key) { + res = "document.key"; + } else if (data.permissions && !decoded?.document?.permissions) { + res = "document.permissions"; + } else if (!decoded?.document?.url) { + res = "document.url"; + } else if (data.documentCallbackUrl && !decoded?.editorConfig?.callbackUrl) { + //todo callbackUrl required + res = "editorConfig.callbackUrl"; + } else if (data.mode && 'view' !== data.mode && !decoded?.editorConfig?.mode) {//allow to restrict rights to 'view' + res = "editorConfig.mode"; + } + return res; + } + function fillDataFromJwt(ctx, decoded, data) { + let res = true; var openCmd = data.openCmd; if (decoded.document) { var doc = decoded.document; @@ -1630,6 +2355,10 @@ exports.install = function(server, callbackFunction) { } } if(doc.permissions) { + res = deepEqual(data.permissions, doc.permissions, {strict: true}); + if (!res) { + ctx.logger.warn('fillDataFromJwt token has modified permissions'); + } if(!data.permissions){ data.permissions = {}; } @@ -1647,6 +2376,9 @@ exports.install = function(server, callbackFunction) { openCmd.url = doc.url; } } + if (null != doc.ds_encrypted) { + data.encrypted = doc.ds_encrypted; + } } if (decoded.editorConfig) { var edit = decoded.editorConfig; @@ -1656,127 +2388,249 @@ exports.install = function(server, callbackFunction) { if (null != edit.lang) { data.lang = edit.lang; } - if (null != edit.mode) { + //allow to restrict rights so don't use token mode in case of 'view' + if (null != edit.mode && 'view' !== data.mode) { data.mode = edit.mode; } - if (null != edit.ds_view) { - data.view = edit.ds_view; + if (edit.coEditing?.mode) { + data.coEditingMode = edit.coEditing.mode; + if (edit.coEditing?.change) { + data.coEditingMode = 'fast'; + } + //offline viewer for pdf|djvu|xps|oxps and embeded + let type = constants.VIEWER_ONLY.exec(decoded.document?.fileType); + if ((type && typeof type[1] === 'string') || "embedded" === decoded.type) { + data.coEditingMode = 'strict'; + } } if (null != edit.ds_isCloseCoAuthoring) { data.isCloseCoAuthoring = edit.ds_isCloseCoAuthoring; } + data.isEnterCorrectPassword = edit.ds_isEnterCorrectPassword; + data.denyChangeName = edit.ds_denyChangeName; + // data.sessionId = edit.ds_sessionId; + data.sessionTimeConnect = edit.ds_sessionTimeConnect; if (edit.user) { var dataUser = data.user; var user = edit.user; - if (null != user.id) { + if (user.id) { dataUser.id = user.id; if (openCmd) { openCmd.userid = user.id; } } - if (null != user.firstname) { + if (null != user.index) { + dataUser.indexUser = user.index; + } + if (user.firstname) { dataUser.firstname = user.firstname; } - if (null != user.lastname) { + if (user.lastname) { dataUser.lastname = user.lastname; } - if (null != user.name) { + if (user.name) { dataUser.username = user.name; } + if (user.group) { + //like in Common.Utils.fillUserInfo(web-apps/apps/common/main/lib/util/utils.js) + dataUser.username = user.group.toString() + String.fromCharCode(160) + dataUser.username; + } + } + if (edit.user && edit.user.name) { + data.denyChangeName = true; } } - //issuer for secret - if (decoded.iss) { - data.iss = decoded.iss; + + //todo make required fields + if (decoded.url || decoded.payload|| (decoded.key && !wopiClient.isWopiJwtToken(decoded))) { + ctx.logger.warn('fillDataFromJwt token has invalid format'); + res = false; } + return res; } - function fillVersionHistoryFromJwt(decoded, cmd) { + function fillVersionHistoryFromJwt(ctx, decoded, data) { + let openCmd = data.openCmd; + data.mode = 'view'; + data.coEditingMode = 'strict'; + data.docid = decoded.key; + openCmd.url = decoded.url; if (decoded.changesUrl && decoded.previous) { - if (decoded.previous.url) { - cmd.setUrl(decoded.previous.url); - } - if (decoded.previous.key) { - cmd.setDocId(decoded.previous.key); - } - } else { - if (decoded.url) { - cmd.setUrl(decoded.url); - } - if (decoded.key) { - cmd.setDocId(decoded.key); + let versionMatch = openCmd.serverVersion === commonDefines.buildVersion; + let openPreviousVersion = openCmd.id === decoded.previous.key; + if (versionMatch && openPreviousVersion) { + data.docid = decoded.previous.key; + openCmd.url = decoded.previous.url; + } else { + ctx.logger.warn('fillVersionHistoryFromJwt serverVersion mismatch or mismatch between previous url and changes. serverVersion=%s docId=%s', openCmd.serverVersion, openCmd.id); } } + return true; } - function fillJwtByConnection(conn) { - var docId = conn.docId; - var payload = {document: {}, editorConfig: {user: {}}}; - var doc = payload.document; - doc.key = conn.docId; - doc.permissions = conn.permissions; - var edit = payload.editorConfig; - //todo - //edit.callbackUrl = callbackUrl; - //edit.lang = conn.lang; - //edit.mode = conn.mode; - var user = edit.user; - user.id = conn.user.idOriginal; - user.name = conn.user.username; - //no standart - edit.ds_view = conn.user.view; - edit.ds_isCloseCoAuthoring = conn.isCloseCoAuthoring; - var options = {algorithm: cfgTokenSessionAlgorithm, expiresIn: cfgTokenSessionExpires / 1000}; - var secret = utils.getSecretByElem(cfgSecretSession); - return jwt.sign(payload, secret, options); - } + function* auth(ctx, conn, data) { + const tenExpUpdateVersionStatus = ms(ctx.getCfg('services.CoAuthoring.expire.updateVersionStatus', cfgExpUpdateVersionStatus)); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenIsAnonymousSupport = ctx.getCfg('services.CoAuthoring.server.isAnonymousSupport', cfgIsAnonymousSupport); + const tenTokenRequiredParams = ctx.getCfg('services.CoAuthoring.server.tokenRequiredParams', cfgTokenRequiredParams); - function* auth(conn, data) { //TODO: Do authorization etc. check md5 or query db + ctx.logger.debug('auth time: %d', data.time); if (data.token && data.user) { - let docId = data.docid; + ctx.setUserId(data.user.id); + let [licenseInfo] = yield tenantManager.getTenantLicense(ctx); + let isDecoded = false; //check jwt - if (cfgTokenEnableBrowser) { - const isSession = !!data.jwtSession; - const checkJwtRes = checkJwt(docId, data.jwtSession || data.jwtOpen, isSession); + if (tenTokenEnableBrowser) { + let secretType = !!data.jwtSession ? commonDefines.c_oAscSecretType.Session : commonDefines.c_oAscSecretType.Browser; + const checkJwtRes = yield checkJwt(ctx, data.jwtSession || data.jwtOpen, secretType); if (checkJwtRes.decoded) { - fillDataFromJwt(checkJwtRes.decoded, data); + isDecoded = true; + let decoded = checkJwtRes.decoded; + let fillDataFromJwtRes = false; + if (wopiClient.isWopiJwtToken(decoded)) { + //wopi + fillDataFromJwtRes = fillDataFromWopiJwt(decoded, data); + } else if (decoded.editorConfig && undefined !== decoded.editorConfig.ds_sessionTimeConnect) { + //reconnection + fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data); + } else if (decoded.version) {//version required, but maybe add new type like jwtSession? + //version history + fillDataFromJwtRes = fillVersionHistoryFromJwt(ctx, decoded, data); + } else { + //opening + let validationErr = validateAuthToken(data, decoded); + if (!validationErr) { + fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data); + } else { + ctx.logger.error("auth missing required parameter %s (since 7.1 version)", validationErr); + if (tenTokenRequiredParams) { + sendDataDisconnectReason(ctx, conn, constants.JWT_ERROR_CODE, constants.JWT_ERROR_REASON); + conn.disconnect(true); + return; + } else { + fillDataFromJwtRes = fillDataFromJwt(ctx, decoded, data); + } + } + } + if(!fillDataFromJwtRes) { + ctx.logger.warn("fillDataFromJwt return false"); + sendDataDisconnectReason(ctx, conn, constants.ACCESS_DENIED_CODE, constants.ACCESS_DENIED_REASON); + conn.disconnect(true); + return; + } } else { - conn.close(checkJwtRes.code, checkJwtRes.description); + sendDataDisconnectReason(ctx, conn, checkJwtRes.code, checkJwtRes.description); + conn.disconnect(true); return; } } + ctx.setUserId(data.user.id); - docId = data.docid; + let docId = data.docid; const user = data.user; + let wopiParams = null, wopiParamsFull = null, openedAtStr; + if (data.documentCallbackUrl) { + wopiParams = wopiClient.parseWopiCallback(ctx, data.documentCallbackUrl); + if (wopiParams && wopiParams.userAuth) { + conn.access_token_ttl = wopiParams.userAuth.access_token_ttl; + } + } + let cmd = null; + if (data.openCmd) { + cmd = new commonDefines.InputCommand(data.openCmd); + cmd.setDocId(docId); + if (isDecoded) { + cmd.setWithAuthorization(true); + } + } + //todo minimize select calls on opening + let result = yield taskResult.select(ctx, docId); + let resultRow = result.length > 0 ? result[0] : null; + if (wopiParams) { + if (resultRow && resultRow.callback) { + wopiParamsFull = wopiClient.parseWopiCallback(ctx, data.documentCallbackUrl, resultRow.callback); + cmd?.setWopiParams(wopiParamsFull); + } + if (!wopiParamsFull || !wopiParamsFull.userAuth || !wopiParamsFull.commonInfo) { + ctx.logger.warn('invalid wopi callback (maybe postgres<9.5) %j', wopiParams); + sendDataDisconnectReason(ctx, conn, constants.DROP_CODE, constants.DROP_REASON); + conn.disconnect(true); + return; + } + } //get user index const bIsRestore = null != data.sessionId; - const cmd = data.openCmd ? new commonDefines.InputCommand(data.openCmd) : null; let upsertRes = null; - let curIndexUser; + let curIndexUser, documentCallback; if (bIsRestore) { - // ЕÑли воÑÑтанавливаем, Ð¸Ð½Ð´ÐµÐºÑ Ñ‚Ð¾Ð¶Ðµ воÑÑтанавливаем + // If we restore, we also restore the index curIndexUser = user.indexUser; } else { - upsertRes = yield canvasService.commandOpenStartPromise(docId, cmd, true, data.documentCallbackUrl, utils.getBaseUrlByConnection(conn)); - curIndexUser = upsertRes.affectedRows == 1 ? 1 : upsertRes.insertId; + if (data.documentCallbackUrl && !wopiParams) { + documentCallback = url.parse(data.documentCallbackUrl); + let filterStatus = yield* utils.checkHostFilter(ctx, documentCallback.hostname); + if (0 !== filterStatus) { + ctx.logger.warn('checkIpFilter error: url = %s', data.documentCallbackUrl); + sendDataDisconnectReason(ctx, conn, constants.DROP_CODE, constants.DROP_REASON); + conn.disconnect(true); + return; + } + } + let format = data.openCmd && data.openCmd.format; + upsertRes = yield canvasService.commandOpenStartPromise(ctx, docId, utils.getBaseUrlByConnection(ctx, conn), data.documentCallbackUrl, format); + curIndexUser = upsertRes.insertId; + //todo update additional in commandOpenStartPromise + if ((upsertRes.isInsert || (wopiParams && 2 === curIndexUser)) && (undefined !== data.timezoneOffset || data.headingsColor || ctx.shardKey || ctx.wopiSrc)) { + //todo insert in commandOpenStartPromise. insert here for database compatibility + if (false === canvasService.hasAdditionalCol) { + let selectRes = yield taskResult.select(ctx, docId); + canvasService.hasAdditionalCol = selectRes.length > 0 && undefined !== selectRes[0].additional; + } + if (canvasService.hasAdditionalCol) { + let task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; + task.key = docId; + if (undefined !== data.timezoneOffset || data.headingsColor) { + //todo duplicate created_at because CURRENT_TIMESTAMP uses server timezone + openedAtStr = sqlBase.DocumentAdditional.prototype.setOpenedAt(Date.now(), data.timezoneOffset, data.headingsColor); + task.additional = openedAtStr; + } + if (ctx.shardKey) { + task.additional += sqlBase.DocumentAdditional.prototype.setShardKey(ctx.shardKey); + } + if (ctx.wopiSrc) { + task.additional += sqlBase.DocumentAdditional.prototype.setWopiSrc(ctx.wopiSrc); + } + yield taskResult.update(ctx, task); + } else { + ctx.logger.warn('auth unknown column "additional"'); + } + } } - if (constants.CONN_CLOSED === conn.readyState) { + if (constants.CONN_CLOSED === conn.conn.readyState) { //closing could happen during async action return; } - const curUserId = user.id + curIndexUser; + const curUserIdOriginal = String(user.id); + const curUserId = curUserIdOriginal + curIndexUser; + conn.tenant = tenantManager.getTenantByConnection(ctx, conn); conn.docId = data.docid; conn.permissions = data.permissions; conn.user = { id: curUserId, - idOriginal: user.id, - username: fillUsername(data), + idOriginal: curUserIdOriginal, + username: fillUsername(ctx, data), indexUser: curIndexUser, - view: !isEditMode(data.permissions, data.mode, !data.view) + view: !isEditMode(data.permissions, data.mode) }; + if (conn.user.view && utils.isLiveViewerSupport(licenseInfo)) { + conn.coEditingMode = data.coEditingMode; + } conn.isCloseCoAuthoring = data.isCloseCoAuthoring; + conn.isEnterCorrectPassword = data.isEnterCorrectPassword; + conn.denyChangeName = data.denyChangeName; conn.editorType = data['editorType']; if (data.sessionTimeConnect) { conn.sessionTimeConnect = data.sessionTimeConnect; @@ -1784,359 +2638,526 @@ exports.install = function(server, callbackFunction) { if (data.sessionTimeIdle >= 0) { conn.sessionTimeLastAction = new Date().getTime() - data.sessionTimeIdle; } + conn.unsyncTime = null; + conn.encrypted = data.encrypted; + conn.lang = data.lang; + conn.supportAuthChangesAck = data.supportAuthChangesAck; + + const c_LR = constants.LICENSE_RESULT; + conn.licenseType = c_LR.Success; + let isLiveViewer = utils.isLiveViewer(conn); + if (!conn.user.view || isLiveViewer) { + let licenseType = yield* _checkLicenseAuth(ctx, licenseInfo, conn.user.idOriginal, isLiveViewer); + let aggregationCtx, licenseInfoAggregation; + if ((c_LR.Success === licenseType || c_LR.SuccessLimit === licenseType) && tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) { + //check server aggregation license + aggregationCtx = new operationContext.Context(); + aggregationCtx.init(tenantManager.getDefautTenant(), ctx.docId, ctx.userId); + //yield ctx.initTenantCache(); //no need + licenseInfoAggregation = tenantManager.getServerLicense(); + licenseType = yield* _checkLicenseAuth(aggregationCtx, licenseInfoAggregation, `${ctx.tenant}:${ conn.user.idOriginal}`, isLiveViewer); + } + conn.licenseType = licenseType; + if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || (!tenIsAnonymousSupport && data.IsAnonymousUser)) { + if (!tenIsAnonymousSupport && data.IsAnonymousUser) { + //do not modify the licenseType because this information is already sent in _checkLicense + ctx.logger.error('auth: access to editor or live viewer is denied for anonymous users'); + } + modifyConnectionEditorToView(ctx, conn); + } else { + //don't check IsAnonymousUser via jwt because substituting it doesn't lead to any trouble + yield* updateEditUsers(ctx, licenseInfo, conn.user.idOriginal, !!data.IsAnonymousUser, isLiveViewer); + if (aggregationCtx && licenseInfoAggregation) { + //update server aggregation license + yield* updateEditUsers(aggregationCtx, licenseInfoAggregation, `${ctx.tenant}:${ conn.user.idOriginal}`, !!data.IsAnonymousUser, isLiveViewer); + } + } + } - // СитуациÑ, когда пользователь уже отключен от ÑовмеÑтного Ñ€ÐµÐ´Ð°ÐºÑ‚Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ + // Situation when the user is already disabled from co-authoring if (bIsRestore && data.isCloseCoAuthoring) { - // УдалÑем предыдущие ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ + conn.sessionId = data.sessionId;//restore old + // delete previous connections connections = _.reject(connections, function(el) { return el.sessionId === data.sessionId;//Delete this connection }); - // Кладем в маÑÑив, Ñ‚.к. нам нужно отправлÑть данные Ð´Ð»Ñ Ð¾Ñ‚ÐºÑ€Ñ‹Ñ‚Ð¸Ñ/ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð° - connections.push(conn); - yield* updatePresence(docId, conn.user.id, getConnectionInfo(conn)); - // ПоÑылаем формальную авторизацию, чтобы подтвердить Ñоединение - yield* sendAuthInfo(undefined, undefined, conn, undefined); - if (cmd) { - yield canvasService.openDocument(conn, cmd, upsertRes); + //closing could happen during async action + if (constants.CONN_CLOSED !== conn.conn.readyState) { + // We put it in an array, because we need to send data to open/save the document + connections.push(conn); + yield addPresence(ctx, conn, true); + // Sending a formal authorization to confirm the connection + yield* sendAuthInfo(ctx, conn, bIsRestore, undefined); + if (cmd) { + yield canvasService.openDocument(ctx, conn, cmd, upsertRes, bIsRestore); + } } return; } - + if (conn.user.idOriginal.length > constants.USER_ID_MAX_LENGTH) { + //todo refactor DB and remove restrictions + ctx.logger.warn('auth user id too long actual = %s; max = %s', curUserIdOriginal.length, constants.USER_ID_MAX_LENGTH); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'User id too long'); + return; + } + if (!conn.user.view) { + var status = result && result.length > 0 ? result[0]['status'] : null; + if (commonDefines.FileStatus.Ok === status) { + // Everything is fine, the status does not need to be updated + } else if (commonDefines.FileStatus.SaveVersion === status || + (!bIsRestore && commonDefines.FileStatus.UpdateVersion === status && + Date.now() - result[0]['status_info'] * 60000 > tenExpUpdateVersionStatus)) { + let newStatus = commonDefines.FileStatus.Ok; + if (commonDefines.FileStatus.UpdateVersion === status) { + ctx.logger.warn("UpdateVersion expired"); + //FileStatus.None to open file again from new url + newStatus = commonDefines.FileStatus.None; + } + // Update the status of the file (the build is in progress, you need to stop it) + var updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = docId; + updateMask.status = status; + updateMask.statusInfo = result[0]['status_info']; + var updateTask = new taskResult.TaskResultData(); + updateTask.status = newStatus; + updateTask.statusInfo = constants.NO_ERROR; + var updateIfRes = yield taskResult.updateIf(ctx, updateTask, updateMask); + if (!(updateIfRes.affectedRows > 0)) { + // error version + //log level is debug because error handled via refreshFile + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE, true); + return; + } + } else if (commonDefines.FileStatus.UpdateVersion === status) { + modifyConnectionEditorToView(ctx, conn); + conn.isCloseCoAuthoring = true; + if (bIsRestore) { + // error version + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Update Version error', constants.UPDATE_VERSION_CODE, true); + return; + } + } else if (commonDefines.FileStatus.None === status && conn.encrypted) { + //ok + } else if (bIsRestore) { + // Other error + if(null === status) { + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Other error', constants.NO_CACHE_CODE, true); + } else { + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Other error'); + } + return; + } + } //Set the unique ID if (bIsRestore) { - logger.info("restored old session: docId = %s id = %s", docId, data.sessionId); + ctx.logger.info("restored old session: id = %s", data.sessionId); if (!conn.user.view) { - // ОÑтанавливаем Ñборку (вдруг она началаÑÑŒ) - // Когда переподÑоединение, нам нужна проверка на Ñборку файла + // Stop the assembly (suddenly it started) + // When reconnecting, we need to check for file assembly try { - var result = yield sqlBase.checkStatusFilePromise(docId); - - var status = result && result.length > 0 ? result[0]['status'] : null; - if (taskResult.FileStatus.Ok === status) { - // Ð’Ñе хорошо, ÑÑ‚Ð°Ñ‚ÑƒÑ Ð¾Ð±Ð½Ð¾Ð²Ð»Ñть не нужно - } else if (taskResult.FileStatus.SaveVersion === status) { - // Обновим ÑÑ‚Ð°Ñ‚ÑƒÑ Ñ„Ð°Ð¹Ð»Ð° (идет Ñборка, нужно ее оÑтановить) - var updateMask = new taskResult.TaskResultData(); - updateMask.key = docId; - updateMask.status = status; - updateMask.statusInfo = result[0]['status_info']; - var updateTask = new taskResult.TaskResultData(); - updateTask.status = taskResult.FileStatus.Ok; - updateTask.statusInfo = constants.NO_ERROR; - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); - if (!(updateIfRes.affectedRows > 0)) { - // error version - sendFileError(conn, 'Update Version error'); - return; - } - } else if (taskResult.FileStatus.UpdateVersion === status) { - // error version - sendFileError(conn, 'Update Version error'); - return; - } else { - // Other error - sendFileError(conn, 'Other error'); - return; - } - - var objChangesDocument = yield* getDocumentChanges(docId); + var puckerIndex = yield* getChangesIndex(ctx, docId); var bIsSuccessRestore = true; - if (objChangesDocument && 0 < objChangesDocument.arrChanges.length) { + if (puckerIndex > 0) { + let objChangesDocument = yield* getDocumentChanges(ctx, docId, puckerIndex - 1, puckerIndex); var change = objChangesDocument.arrChanges[objChangesDocument.getLength() - 1]; - if (change['change']) { - if (change['user'] !== curUserId) { - bIsSuccessRestore = 0 === (((data['lastOtherSaveTime'] - change['time']) / 1000) >> 0); + if (change) { + if (change['change']) { + if (change['user'] !== curUserId) { + bIsSuccessRestore = 0 === (((data['lastOtherSaveTime'] - change['time']) / 1000) >> 0); + } } + } else { + bIsSuccessRestore = false; } } if (bIsSuccessRestore) { - // ПроверÑем lock-и + // check locks var arrayBlocks = data['block']; - var getLockRes = yield* getLock(conn, data, true); + var getLockRes = yield getLock(ctx, conn, data, true); if (arrayBlocks && (0 === arrayBlocks.length || getLockRes)) { - yield* authRestore(conn, data.sessionId); + let wopiLockRes = true; + if (wopiParamsFull) { + wopiLockRes = yield wopiClient.lock(ctx, 'LOCK', wopiParamsFull.commonInfo.lockId, + wopiParamsFull.commonInfo.fileInfo, wopiParamsFull.userAuth); + } + if (wopiLockRes) { + yield* authRestore(ctx, conn, data.sessionId); + } else { + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Restore error. Wopi lock error.', constants.RESTORE_CODE, true); + } } else { - sendFileError(conn, 'Restore error. Locks not checked.'); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Restore error. Locks not checked.', constants.RESTORE_CODE, true); } } else { - sendFileError(conn, 'Restore error. Document modified.'); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'Restore error. Document modified.', constants.RESTORE_CODE, true); } } catch (err) { - logger.error("DataBase error: docId = %s %s", docId, err.stack); - sendFileError(conn, 'DataBase error'); + ctx.logger.error("DataBase error: %s", err.stack); + yield* sendFileErrorAuth(ctx, conn, data.sessionId, 'DataBase error', constants.RESTORE_CODE, true); } } else { - yield* authRestore(conn, data.sessionId); + yield* authRestore(ctx, conn, data.sessionId); } } else { conn.sessionId = conn.id; - const endAuthRes = yield* endAuth(conn, false, data.documentCallbackUrl); + let openedAt = openedAtStr ? sqlBase.DocumentAdditional.prototype.getOpenedAt(openedAtStr) : canvasService.getOpenedAt(resultRow); + const endAuthRes = yield* endAuth(ctx, conn, false, documentCallback, openedAt); if (endAuthRes && cmd) { - yield canvasService.openDocument(conn, cmd, upsertRes); + //todo to allow forcesave TemplateSource after convertion(move to better place) + if (wopiParamsFull?.commonInfo?.fileInfo?.TemplateSource) { + let newChangesLastDate = new Date(); + newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding + cmd.setExternalChangeInfo(getExternalChangeInfo(conn.user, newChangesLastDate.getTime(), conn.lang)); + } + yield canvasService.openDocument(ctx, conn, cmd, upsertRes, bIsRestore); } } } } - function* endAuth(conn, bIsRestore, documentCallbackUrl) { - var res = true; - var docId = conn.docId; - var tmpUser = conn.user; - if (constants.CONN_CLOSED === conn.readyState) { + function* endAuth(ctx, conn, bIsRestore, documentCallback, opt_openedAt) { + const tenExpLockDoc = ctx.getCfg('services.CoAuthoring.expire.lockDoc', cfgExpLockDoc); + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + + let res = true; + const docId = conn.docId; + const tmpUser = conn.user; + let hasForgotten; + if (constants.CONN_CLOSED === conn.conn.readyState) { //closing could happen during async action return false; } connections.push(conn); - var firstParticipantNoView, countNoView = 0; - var participantsMap = yield* getParticipantMap(docId, tmpUser.id, getConnectionInfo(conn)); - for (var i = 0; i < participantsMap.length; ++i) { - var elem = participantsMap[i]; + let firstParticipantNoView, countNoView = 0; + yield addPresence(ctx, conn, true); + let participantsMap = yield getParticipantMap(ctx, docId); + const participantsTimestamp = Date.now(); + for (let i = 0; i < participantsMap.length; ++i) { + const elem = participantsMap[i]; if (!elem.view) { ++countNoView; - if (!firstParticipantNoView && elem.id != tmpUser.id) { + if (!firstParticipantNoView && elem.id !== tmpUser.id) { firstParticipantNoView = elem; } } } - - // ОтправлÑем на внешний callback только Ð´Ð»Ñ Ñ‚ÐµÑ…, кто редактирует - var bindEventsRes = commonDefines.c_oAscServerCommandErrors.NoError; + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return false; + } + // Sending to an external callback only for those who edit if (!tmpUser.view) { - var userAction = new commonDefines.OutputAction(commonDefines.c_oAscUserAction.In, tmpUser.idOriginal); - // ЕÑли пришла Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¾ ÑÑылке Ð´Ð»Ñ Ð¿Ð¾ÑÑ‹Ð»Ð°Ð½Ð¸Ñ Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ð¸, то добавлÑем - if (documentCallbackUrl) { - bindEventsRes = yield* bindEvents(docId, documentCallbackUrl, conn.baseUrl, userAction); - } else { - yield* sendStatusDocument(docId, c_oAscChangeBase.No, userAction); + const userIndex = utils.getIndexFromUserId(tmpUser.id, tmpUser.idOriginal); + const userAction = new commonDefines.OutputAction(commonDefines.c_oAscUserAction.In, tmpUser.idOriginal); + //make async request to speed up file opening + sendStatusDocument(ctx, docId, c_oAscChangeBase.No, userAction, userIndex, documentCallback, conn.baseUrl) + .catch(err => ctx.logger.error('endAuth sendStatusDocument error: %s', err.stack)); + if (!bIsRestore) { + //check forgotten file + let forgotten = yield storage.listObjects(ctx, docId, tenForgottenFiles); + hasForgotten = forgotten.length > 0; + ctx.logger.debug('endAuth hasForgotten %s', hasForgotten); } } - if (commonDefines.c_oAscServerCommandErrors.NoError === bindEventsRes) { - var lockDocument = null; - if (!bIsRestore && 2 === countNoView && !tmpUser.view) { - // Ставим lock на документ - var isLock = yield utils.promiseRedis(redisClient, redisClient.setnx, - redisKeyLockDoc + docId, JSON.stringify(firstParticipantNoView)); - if (isLock) { - lockDocument = firstParticipantNoView; - yield utils.promiseRedis(redisClient, redisClient.expire, redisKeyLockDoc + docId, cfgExpLockDoc); + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return false; + } + let lockDocument = null; + let waitAuthUserId; + if (!bIsRestore && 2 === countNoView && !tmpUser.view && firstParticipantNoView) { + // lock a document + const lockRes = yield editorData.lockAuth(ctx, docId, firstParticipantNoView.id, 2 * tenExpLockDoc); + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return false; + } + if (lockRes) { + lockDocument = firstParticipantNoView; + waitAuthUserId = lockDocument.id; + let lockDocumentTimer = lockDocumentsTimerId[docId]; + if (lockDocumentTimer) { + cleanLockDocumentTimer(docId, lockDocumentTimer); } + yield* setLockDocumentTimer(ctx, docId, lockDocument.id); + } + } + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return false; + } + if (lockDocument && !tmpUser.view) { + // waiting for the editor to switch to co-editing mode + const sendObject = { + type: "waitAuth", + lockDocument: lockDocument + }; + sendData(ctx, conn, sendObject);//Or 0 if fails + } else { + if (!bIsRestore && needSendChanges(conn)) { + yield* sendAuthChanges(ctx, conn.docId, [conn]); } - if (!lockDocument) { - var getRes = yield utils.promiseRedis(redisClient, redisClient.get, redisKeyLockDoc + docId); - if (getRes) { - var getResParsed = JSON.parse(getRes); - //prevent self locking - if (tmpUser.id !== getResParsed.id) { - lockDocument = getResParsed; + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return false; + } + yield* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, hasForgotten, opt_openedAt); + } + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return false; + } + yield publish(ctx, {type: commonDefines.c_oPublishType.participantsState, ctx: ctx, docId: docId, userId: tmpUser.id, participantsTimestamp: participantsTimestamp, participants: participantsMap, waitAuthUserId: waitAuthUserId}, docId, tmpUser.id); + return res; + } + + function* saveErrorChanges(ctx, docId, destDir) { + const tenEditor = getEditorConfig(ctx); + const tenMaxRequestChanges = ctx.getCfg('services.CoAuthoring.server.maxRequestChanges', cfgMaxRequestChanges); + const tenErrorFiles = ctx.getCfg('FileConverter.converter.errorfiles', cfgErrorFiles); + + let index = 0; + let indexChunk = 1; + let changes; + let changesPrefix = destDir + '/' + constants.CHANGES_NAME + '/' + constants.CHANGES_NAME + '.json.'; + do { + changes = yield sqlBase.getChangesPromise(ctx, docId, index, index + tenMaxRequestChanges); + if (changes.length > 0) { + let buffer; + if (tenEditor['binaryChanges']) { + let buffers = changes.map(elem => elem.change_data); + buffers.unshift(Buffer.from(utils.getChangesFileHeader(), 'utf-8')) + buffer = Buffer.concat(buffers); + } else { + let changesJSON = indexChunk > 1 ? ',[' : '['; + changesJSON += changes[0].change_data; + for (let i = 1; i < changes.length; ++i) { + changesJSON += ','; + changesJSON += changes[i].change_data; } + changesJSON += ']\r\n'; + buffer = Buffer.from(changesJSON, 'utf8'); } + yield storage.putObject(ctx, changesPrefix + (indexChunk++).toString().padStart(3, '0'), buffer, buffer.length, tenErrorFiles); } + index += tenMaxRequestChanges; + } while (changes && tenMaxRequestChanges === changes.length); + } - if (lockDocument && !tmpUser.view) { - // Ð”Ð»Ñ view не ждем ÑнÑÑ‚Ð¸Ñ lock-а - var sendObject = { - type: "waitAuth", - lockDocument: lockDocument + function sendAuthChangesByChunks(ctx, changes, connections) { + return co(function* () { + //websocket payload size is limited by https://github.com/faye/faye-websocket-node#initialization-options (64 MiB) + //xhr payload size is limited by nginx param client_max_body_size (current 100MB) + //"1.5MB" is choosen to avoid disconnect(after 25s) while downloading/uploading oversized changes with 0.5Mbps connection + const tenEditor = getEditorConfig(ctx); + + let startIndex = 0; + let endIndex = 0; + while (endIndex < changes.length) { + startIndex = endIndex; + let curBytes = 0; + for (; endIndex < changes.length && curBytes < tenEditor['websocketMaxPayloadSize']; ++endIndex) { + curBytes += JSON.stringify(changes[endIndex]).length + 24;//24 - for JSON overhead + } + //todo simplify 'authChanges' format to reduce message size and JSON overhead + const sendObject = { + type: 'authChanges', + changes: changes.slice(startIndex, endIndex) }; - sendData(conn, sendObject);//Or 0 if fails - } else { - if (bIsRestore) { - yield* sendAuthInfo(undefined, undefined, conn, participantsMap); - } else { - var objChangesDocument = yield* getDocumentChanges(docId); - yield* sendAuthInfo(objChangesDocument.arrChanges, objChangesDocument.getLength(), conn, participantsMap); + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + if (needSendChanges(conn)) { + if (conn.supportAuthChangesAck) { + conn.authChangesAck = true; + } + sendData(ctx, conn, sendObject); + } + } + //todo use emit callback + //wait ack + let time = 0; + let interval = 100; + let limit = 30000; + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + while (constants.CONN_CLOSED !== conn.readyState && needSendChanges(conn) && conn.authChangesAck && time < limit) { + yield utils.sleep(interval); + time += interval; + } + delete conn.authChangesAck; } } - yield* publish({type: commonDefines.c_oPublishType.participantsState, docId: docId, user: tmpUser, state: true}, docId, tmpUser.id); - } else { - sendFileError(conn, 'ip filter'); - res = false; - } - return res; + }); + } + function* sendAuthChanges(ctx, docId, connections) { + const tenMaxRequestChanges = ctx.getCfg('services.CoAuthoring.server.maxRequestChanges', cfgMaxRequestChanges); + + let index = 0; + let changes; + do { + let objChangesDocument = yield getDocumentChanges(ctx, docId, index, index + tenMaxRequestChanges); + changes = objChangesDocument.arrChanges; + yield sendAuthChangesByChunks(ctx, changes, connections); + connections = connections.filter((conn) => { + return constants.CONN_CLOSED !== conn.readyState; + }); + index += tenMaxRequestChanges; + } while (connections.length > 0 && changes && tenMaxRequestChanges === changes.length); } + function* sendAuthInfo(ctx, conn, bIsRestore, participantsMap, opt_hasForgotten, opt_openedAt) { + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize); + const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload); - function* sendAuthInfo(objChangesDocument, changesIndex, conn, participantsMap) { const docId = conn.docId; - let docLock; - if(EditorTypes.document == conn.editorType){ - docLock = {}; - let elem; - const allLocks = yield* getAllLocks(docId); - for(let i = 0 ; i < allLocks.length; ++i) { - elem = allLocks[i]; - docLock[elem.block] = elem; + let docLock = yield editorData.getLocks(ctx, docId); + if (EditorTypes.document !== conn.editorType){ + let docLockList = []; + for (let lockId in docLock) { + docLockList.push(docLock[lockId]); } - } else { - docLock = yield* getAllLocks(docId); + docLock = docLockList; } - const allMessages = yield utils.promiseRedis(redisClient, redisClient.lrange, redisKeyMessage + docId, 0, -1); - let allMessagesParsed = undefined; - if(allMessages && allMessages.length > 0) { - allMessagesParsed = allMessages.map(function (val) { - return JSON.parse(val); - }); + let allMessages = yield editorData.getMessages(ctx, docId); + allMessages = allMessages.length > 0 ? allMessages : undefined;//todo client side + let sessionToken; + if (tenTokenEnableBrowser && !bIsRestore) { + sessionToken = yield fillJwtByConnection(ctx, conn); } + let tenEditor = getEditorConfig(ctx); + tenEditor["limits_image_size"] = tenImageSize; + tenEditor["limits_image_types_upload"] = tenTypesUpload; const sendObject = { type: 'auth', result: 1, sessionId: conn.sessionId, sessionTimeConnect: conn.sessionTimeConnect, participants: participantsMap, - messages: allMessagesParsed, + messages: allMessages, locks: docLock, - changes: objChangesDocument, - changesIndex: changesIndex, indexUser: conn.user.indexUser, - jwt: cfgTokenEnableBrowser ? {token: fillJwtByConnection(conn), expires: cfgTokenSessionExpires} : undefined, - g_cAscSpellCheckUrl: cfgSpellcheckerUrl, + hasForgotten: opt_hasForgotten, + jwt: sessionToken, + g_cAscSpellCheckUrl: tenEditor["spellcheckerUrl"], buildVersion: commonDefines.buildVersion, - buildNumber: commonDefines.buildNumber + buildNumber: commonDefines.buildNumber, + licenseType: conn.licenseType, + settings: tenEditor, + openedAt: opt_openedAt }; - sendData(conn, sendObject);//Or 0 if fails + sendData(ctx, conn, sendObject);//Or 0 if fails } - function* onMessage(conn, data) { + function* onMessage(ctx, conn, data) { + if (false === conn.permissions?.chat) { + ctx.logger.warn("insert message permissions.chat==false"); + return; + } var docId = conn.docId; var userId = conn.user.id; - var msg = {docid: docId, message: data.message, time: Date.now(), user: userId, username: conn.user.username}; - var msgStr = JSON.stringify(msg); - var multi = redisClient.multi([ - ['rpush', redisKeyMessage + docId, msgStr], - ['expire', redisKeyMessage + docId, cfgExpMessage] - ]); - yield utils.promiseRedis(multi, multi.exec); + var msg = {docid: docId, message: data.message, time: Date.now(), user: userId, useridoriginal: conn.user.idOriginal, username: conn.user.username}; + yield editorData.addMessage(ctx, docId, msg); // insert - logger.info("insert message: docId = %s %s", docId, msgStr); + ctx.logger.info("insert message: %j", msg); var messages = [msg]; - sendDataMessage(conn, messages); - yield* publish({type: commonDefines.c_oPublishType.message, docId: docId, userId: userId, messages: messages}, docId, userId); + sendDataMessage(ctx, conn, messages); + yield publish(ctx, {type: commonDefines.c_oPublishType.message, ctx: ctx, docId: docId, userId: userId, messages: messages}, docId, userId); } - function* onCursor(conn, data) { + function* onCursor(ctx, conn, data) { var docId = conn.docId; var userId = conn.user.id; var msg = {cursor: data.cursor, time: Date.now(), user: userId, useridoriginal: conn.user.idOriginal}; - logger.info("send cursor: docId = %s %s", docId, msg); + ctx.logger.info("send cursor: %s", msg); var messages = [msg]; - yield* publish({type: commonDefines.c_oPublishType.cursor, docId: docId, userId: userId, messages: messages}, docId, userId); + yield publish(ctx, {type: commonDefines.c_oPublishType.cursor, ctx: ctx, docId: docId, userId: userId, messages: messages}, docId, userId); } - - function* getLock(conn, data, bIsRestore) { - logger.info("getLock docid: %s", conn.docId); - var fLock = null; + // For Word block is now string "guid" + // For Excel block is now object { sheetId, type, rangeOrObjectId, guid } + // For presentations, this is an object { type, val } or { type, slideId, objId } + async function getLock(ctx, conn, data, bIsRestore) { + ctx.logger.debug("getLock"); + var fCheckLock = null; switch (conn.editorType) { case EditorTypes.document: // Word - fLock = getLockWord; + fCheckLock = _checkLockWord; break; case EditorTypes.spreadsheet: // Excel - fLock = getLockExcel; + fCheckLock = _checkLockExcel; break; case EditorTypes.presentation: + case EditorTypes.diagram: // PP - fLock = getLockPresentation; + fCheckLock = _checkLockPresentation; break; + default: + return false; } - return fLock ? yield* fLock(conn, data, bIsRestore) : false; - } - - function* getLockWord(conn, data, bIsRestore) { - var docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; - var i; - var checkRes = yield* _checkLock(docId, arrayBlocks); - var documentLocks = checkRes.documentLocks; - if (checkRes.res) { - //Ok. take lock - var toCache = []; - for (i = 0; i < arrayBlocks.length; ++i) { - var block = arrayBlocks[i]; - var elem = {time: Date.now(), user: userId, block: block, sessionId: conn.sessionId}; - documentLocks[block] = elem; - toCache.push(JSON.stringify(elem)); - } - yield* addLocks(docId, toCache); - } else if (bIsRestore) { - return false; - } - //тому кто зделал Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÐ¼ макÑимально быÑтро - sendData(conn, {type: "getLock", locks: documentLocks}); - yield* publish({type: commonDefines.c_oPublishType.getLock, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); - return true; - } - - // Ð”Ð»Ñ Excel block теперь Ñто объект { sheetId, type, rangeOrObjectId, guid } - function* getLockExcel(conn, data, bIsRestore) { - var docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; - var i; - var checkRes = yield* _checkLockExcel(docId, arrayBlocks, userId); - var documentLocks = checkRes.documentLocks; - if (checkRes.res) { - //Ok. take lock - var toCache = []; - for (i = 0; i < arrayBlocks.length; ++i) { - var block = arrayBlocks[i]; - var elem = {time: Date.now(), user: userId, block: block, sessionId: conn.sessionId}; - documentLocks.push(elem); - toCache.push(JSON.stringify(elem)); - } - yield* addLocks(docId, toCache); - } else if (bIsRestore) { - return false; - } - //тому кто зделал Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÐ¼ макÑимально быÑтро - sendData(conn, {type: "getLock", locks: documentLocks}); - yield* publish({type: commonDefines.c_oPublishType.getLock, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); - return true; - } - - // Ð”Ð»Ñ Ð¿Ñ€ÐµÐ·ÐµÐ½Ñ‚Ð°Ñ†Ð¸Ð¹ Ñто объект { type, val } или { type, slideId, objId } - function* getLockPresentation(conn, data, bIsRestore) { - var docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; - var i; - var checkRes = yield* _checkLockPresentation(docId, arrayBlocks, userId); - var documentLocks = checkRes.documentLocks; - if (checkRes.res) { - //Ok. take lock - var toCache = []; - for (i = 0; i < arrayBlocks.length; ++i) { - var block = arrayBlocks[i]; - var elem = {time: Date.now(), user: userId, block: block, sessionId: conn.sessionId}; - documentLocks.push(elem); - toCache.push(JSON.stringify(elem)); - } - yield* addLocks(docId, toCache); - } else if (bIsRestore) { - return false; + let docId = conn.docId, userId = conn.user.id, arrayBlocks = data.block; + let locks = arrayBlocks.reduce(function(map, block) { + //todo use one id + map[block.guid || block] = {time: Date.now(), user: userId, block: block}; + return map; + }, {}); + let addRes = await editorData.addLocksNX(ctx, docId, locks); + let documentLocks = addRes.allLocks; + let isAllAdded = Object.keys(addRes.lockConflict).length === 0; + if (!isAllAdded || !fCheckLock(ctx, docId, documentLocks, locks, arrayBlocks, userId)) { + //remove new locks + let toRemove = {}; + for (let lockId in locks) { + if (!addRes.lockConflict[lockId]) { + toRemove[lockId] = locks[lockId]; + delete documentLocks[lockId]; + } + } + await editorData.removeLocks(ctx, docId, toRemove); + if (bIsRestore) { + return false; + } } - //тому кто зделал Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð²Ð¾Ð·Ð²Ñ€Ð°Ñ‰Ð°ÐµÐ¼ макÑимально быÑтро - sendData(conn, {type: "getLock", locks: documentLocks}); - yield* publish({type: commonDefines.c_oPublishType.getLock, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); + sendData(ctx, conn, {type: "getLock", locks: documentLocks}); + await publish(ctx, {type: commonDefines.c_oPublishType.getLock, ctx: ctx, docId: docId, userId: userId, documentLocks: documentLocks}, docId, userId); return true; } - function sendGetLock(participants, documentLocks) { + function sendGetLock(ctx, participants, documentLocks) { _.each(participants, function(participant) { - sendData(participant, {type: "getLock", locks: documentLocks}); + sendData(ctx, participant, {type: "getLock", locks: documentLocks}); }); } - function* setChangesIndex(docId, index) { - yield utils.promiseRedis(redisClient, redisClient.setex, redisKeyChangeIndex + docId, cfgExpChangeIndex, index); - } + // For Excel, it is necessary to recalculate locks when adding / deleting rows / columns + function* saveChanges(ctx, conn, data) { + const tenEditor = getEditorConfig(ctx); + const tenPubSubMaxChanges = ctx.getCfg('services.CoAuthoring.pubsub.maxChanges', cfgPubSubMaxChanges); + const tenExpSaveLock = ctx.getCfg('services.CoAuthoring.expire.saveLock', cfgExpSaveLock); - // Ð”Ð»Ñ Excel необходимо делать переÑчет lock-ов при добавлении/удалении Ñтрок/Ñтолбцов - function* saveChanges(conn, data) { const docId = conn.docId, userId = conn.user.id; - logger.info("Start saveChanges docid: %s", docId); + ctx.logger.info("Start saveChanges: reSave: %s", data.reSave); + + let lockRes = yield editorData.lockSave(ctx, docId, userId, tenExpSaveLock); + if (!lockRes) { + //should not be here. cfgExpSaveLock - 60sec, sockjs disconnects after 25sec + ctx.logger.warn("saveChanges lockSave error"); + return; + } - let puckerIndex = yield* getChangesIndex(docId); + let puckerIndex = yield* getChangesIndex(ctx, docId); + + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return; + } let deleteIndex = -1; if (data.startSaveChanges && null != data.deleteIndex) { @@ -2145,59 +3166,65 @@ exports.install = function(server, callbackFunction) { const deleteCount = puckerIndex - deleteIndex; if (0 < deleteCount) { puckerIndex -= deleteCount; - yield sqlBase.deleteChangesPromise(docId, deleteIndex); + yield sqlBase.deleteChangesPromise(ctx, docId, deleteIndex); } else if (0 > deleteCount) { - logger.error("Error saveChanges docid: %s ; deleteIndex: %s ; startIndex: %s ; deleteCount: %s", docId, deleteIndex, puckerIndex, deleteCount); + ctx.logger.error("Error saveChanges: deleteIndex: %s ; startIndex: %s ; deleteCount: %s", deleteIndex, puckerIndex, deleteCount); } } } - // Стартовый Ð¸Ð½Ð´ÐµÐºÑ Ð¸Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ добавлении + if (constants.CONN_CLOSED === conn.conn.readyState) { + //closing could happen during async action + return; + } + + // Starting index change when adding const startIndex = puckerIndex; - const newChanges = JSON.parse(data.changes); - let newChangesLastTime = null; + const newChanges = tenEditor['binaryChanges'] ? data.changes : JSON.parse(data.changes); + let newChangesLastDate = new Date(); + newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding + let newChangesLastTime = newChangesLastDate.getTime(); let arrNewDocumentChanges = []; - logger.info("saveChanges docid: %s ; deleteIndex: %s ; startIndex: %s ; length: %s", docId, deleteIndex, startIndex, newChanges.length); + ctx.logger.info("saveChanges: deleteIndex: %s ; startIndex: %s ; length: %s", deleteIndex, startIndex, newChanges.length); if (0 < newChanges.length) { let oElement = null; for (let i = 0; i < newChanges.length; ++i) { oElement = newChanges[i]; - newChangesLastTime = Date.now(); - arrNewDocumentChanges.push({docid: docId, change: JSON.stringify(oElement), time: newChangesLastTime, + let change = tenEditor['binaryChanges'] ? oElement : JSON.stringify(oElement); + arrNewDocumentChanges.push({docid: docId, change: change, time: newChangesLastDate, user: userId, useridoriginal: conn.user.idOriginal}); } puckerIndex += arrNewDocumentChanges.length; - yield sqlBase.insertChangesPromise(arrNewDocumentChanges, docId, startIndex, conn.user); + yield sqlBase.insertChangesPromise(ctx, arrNewDocumentChanges, docId, startIndex, conn.user); } - yield* setChangesIndex(docId, puckerIndex); const changesIndex = (-1 === deleteIndex && data.startSaveChanges) ? startIndex : -1; if (data.endSaveChanges) { - // Ð”Ð»Ñ Excel нужно переÑчитать индекÑÑ‹ Ð´Ð»Ñ lock-ов + // For Excel, you need to recalculate indexes for locks if (data.isExcel && false !== data.isCoAuthoring && data.excelAdditionalInfo) { const tmpAdditionalInfo = JSON.parse(data.excelAdditionalInfo); - // Это мы получили recalcIndexColumns и recalcIndexRows + // This is what we got recalcIndexColumns and recalcIndexRows const oRecalcIndexColumns = _addRecalcIndex(tmpAdditionalInfo["indexCols"]); const oRecalcIndexRows = _addRecalcIndex(tmpAdditionalInfo["indexRows"]); - // Теперь нужно переÑчитать индекÑÑ‹ Ð´Ð»Ñ lock-Ñлементов + // Now we need to recalculate indexes for lock elements if (null !== oRecalcIndexColumns || null !== oRecalcIndexRows) { - const docLock = yield* getAllLocks(docId); - if (_recalcLockArray(userId, docLock, oRecalcIndexColumns, oRecalcIndexRows)) { - let toCache = []; - for (let i = 0; i < docLock.length; ++i) { - toCache.push(JSON.stringify(docLock[i])); - } - yield* addLocks(docId, toCache, true); + let docLock = yield editorData.getLocks(ctx, docId); + let docLockMod = _recalcLockArray(userId, docLock, oRecalcIndexColumns, oRecalcIndexRows); + if (Object.keys(docLockMod).length > 0) { + yield editorData.addLocks(ctx, docId, docLockMod); } } } - //Release locks - const userLocks = yield* getUserLocks(docId, conn.sessionId); - // Ð”Ð»Ñ Ð´Ð°Ð½Ð½Ð¾Ð³Ð¾ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñнимаем Lock Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð°, еÑли пришел флаг unlock - const checkEndAuthLockRes = yield* checkEndAuthLock(data.unlock, false, docId, userId); + let userLocks = []; + if (data.releaseLocks) { + //Release locks + userLocks = yield removeUserLocks(ctx, docId, userId); + } + // For this user, we remove Lock from the document if the unlock flag has arrived + const checkEndAuthLockRes = yield* checkEndAuthLock(ctx, data.unlock, false, docId, userId); if (!checkEndAuthLockRes) { const arrLocks = _.map(userLocks, function(e) { return { @@ -2208,128 +3235,112 @@ exports.install = function(server, callbackFunction) { }; }); let changesToSend = arrNewDocumentChanges; - if(changesToSend.length > cfgPubSubMaxChanges) { + if(changesToSend.length > tenPubSubMaxChanges) { changesToSend = null; + } else { + changesToSend.forEach((value) => { + value.time = value.time.getTime(); + }) } - yield* publish({type: commonDefines.c_oPublishType.changes, docId: docId, userId: userId, - changes: changesToSend, startIndex: startIndex, changesIndex: puckerIndex, - locks: arrLocks, excelAdditionalInfo: data.excelAdditionalInfo}, docId, userId); + yield publish(ctx, {type: commonDefines.c_oPublishType.changes, ctx: ctx, docId: docId, userId: userId, + changes: changesToSend, startIndex: startIndex, changesIndex: puckerIndex, syncChangesIndex: puckerIndex, + locks: arrLocks, excelAdditionalInfo: data.excelAdditionalInfo, endSaveChanges: data.endSaveChanges}, docId, userId); } - // ÐвтоматичеÑки Ñнимаем lock Ñами и поÑылаем Ð¸Ð½Ð´ÐµÐºÑ Ð´Ð»Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - yield* unSaveLock(conn, changesIndex, newChangesLastTime); + // Automatically remove the lock ourselves and send the index to save + yield* unSaveLock(ctx, conn, changesIndex, newChangesLastTime, puckerIndex); //last save - if (newChangesLastTime) { - let commands = [ - ['del', redisKeyForceSave + docId], - ['hmset', redisKeyLastSave + docId, 'time', newChangesLastTime, 'index', puckerIndex, - 'baseUrl', utils.getBaseUrlByConnection(conn)], - ['expire', redisKeyLastSave + docId, cfgExpLastSave] - ]; - if (cfgForceSaveEnable) { - let ttl = Math.ceil((cfgForceSaveInterval + cfgForceSaveStep) / 1000); - let multi = redisClient.multi([ - ['setnx', redisKeyForceSaveTimerLock + docId, 1], - ['expire', redisKeyForceSaveTimerLock + docId, ttl] - ]); - let multiRes = yield utils.promiseRedis(multi, multi.exec); - if (multiRes[0]) { - let expireAt = newChangesLastTime + cfgForceSaveInterval; - commands.push(['zadd', redisKeyForceSaveTimer, expireAt, docId]); - } - } - let multi = redisClient.multi(commands); - yield utils.promiseRedis(multi, multi.exec); - } + let changeInfo = getExternalChangeInfo(conn.user, newChangesLastTime, conn.lang); + yield resetForceSaveAfterChanges(ctx, docId, newChangesLastTime, puckerIndex, utils.getBaseUrlByConnection(ctx, conn), changeInfo); } else { - let changesToSend = arrNewDocumentChanges; - if(changesToSend.length > cfgPubSubMaxChanges) { + let changesToSend = arrNewDocumentChanges; + if(changesToSend.length > tenPubSubMaxChanges) { changesToSend = null; + } else { + changesToSend.forEach((value) => { + value.time = value.time.getTime(); + }) + } + let isPublished = yield publish(ctx, {type: commonDefines.c_oPublishType.changes, ctx: ctx, docId: docId, userId: userId, + changes: changesToSend, startIndex: startIndex, changesIndex: puckerIndex, syncChangesIndex: puckerIndex, + locks: [], excelAdditionalInfo: undefined, endSaveChanges: data.endSaveChanges}, docId, userId); + sendData(ctx, conn, {type: 'savePartChanges', changesIndex: changesIndex, syncChangesIndex: puckerIndex}); + if (!isPublished) { + //stub for lockDocumentsTimerId + yield publish(ctx, {type: commonDefines.c_oPublishType.changesNotify, ctx: ctx, docId: docId}); } - yield* publish({type: commonDefines.c_oPublishType.changes, docId: docId, userId: userId, - changes: changesToSend, startIndex: startIndex, changesIndex: puckerIndex, - locks: [], excelAdditionalInfo: undefined}, docId, userId); - sendData(conn, {type: 'savePartChanges', changesIndex: changesIndex}); } } - // Можем ли мы ÑохранÑть ? - function* isSaveLock(conn) { - let isSaveLock = true; - const exist = yield utils.promiseRedis(redisClient, redisClient.setnx, redisKeySaveLock + conn.docId, conn.user.id); - if (exist) { - isSaveLock = false; - const saveLock = yield utils.promiseRedis(redisClient, redisClient.expire, redisKeySaveLock + conn.docId, cfgExpSaveLock); + // Can we save? + function* isSaveLock(ctx, conn, data) { + const tenExpSaveLock = ctx.getCfg('services.CoAuthoring.expire.saveLock', cfgExpSaveLock); + + if (!conn.user) { + return; } + let lockRes = true; + //check changesIndex for compatibility or 0 in case of first save + if (data.syncChangesIndex) { + let forceSave = yield editorData.getForceSave(ctx, conn.docId); + if (forceSave && forceSave.index !== data.syncChangesIndex) { + if (!conn.unsyncTime) { + conn.unsyncTime = new Date(); + } + if (Date.now() - conn.unsyncTime.getTime() < tenExpSaveLock * 1000) { + lockRes = false; + ctx.logger.debug("isSaveLock editor unsynced since %j serverIndex:%s clientIndex:%s ", conn.unsyncTime, forceSave.index, data.syncChangesIndex); + sendData(ctx, conn, {type: "saveLock", saveLock: !lockRes}); + return; + } else { + ctx.logger.warn("isSaveLock editor unsynced since %j serverIndex:%s clientIndex:%s ", conn.unsyncTime, forceSave.index, data.syncChangesIndex); + } + } + } + conn.unsyncTime = null; - // ОтправлÑем только тому, кто Ñпрашивал (вÑем отправлÑть нельзÑ) - sendData(conn, {type: "saveLock", saveLock: isSaveLock}); - } + lockRes = yield editorData.lockSave(ctx, conn.docId, conn.user.id, tenExpSaveLock); + ctx.logger.debug("isSaveLock lockRes: %s", lockRes); - // Снимаем лок Ñ ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ - function* unSaveLock(conn, index, time) { - const saveLock = yield utils.promiseRedis(redisClient, redisClient.get, redisKeySaveLock + conn.docId); - // ToDo проверка null === saveLock Ñто заглушка на подключение второго Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ð² документ (не делаетÑÑ saveLock в Ñтот момент, но идет Ñохранение и ÑнÑть его нужно) - if (null === saveLock || conn.user.id == saveLock) { - yield utils.promiseRedis(redisClient, redisClient.del, redisKeySaveLock + conn.docId); - sendData(conn, {type: 'unSaveLock', index: index, time: time}); - } + // We send only to the one who asked (you can not send to everyone) + sendData(ctx, conn, {type: "saveLock", saveLock: !lockRes}); } - // Возвращаем вÑе ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð´Ð»Ñ Ð´Ð¾ÐºÑƒÐ¼ÐµÐ½Ñ‚Ð° - function* getMessages(conn) { - const allMessages = yield utils.promiseRedis(redisClient, redisClient.lrange, redisKeyMessage + conn.docId, 0, -1); - let allMessagesParsed = undefined; - if(allMessages && allMessages.length > 0) { - allMessagesParsed = allMessages.map(function (val) { - return JSON.parse(val); - }); + // Removing lock from save + function* unSaveLock(ctx, conn, index, time, syncChangesIndex) { + var unlockRes = yield editorData.unlockSave(ctx, conn.docId, conn.user.id); + if (commonDefines.c_oAscUnlockRes.Locked !== unlockRes) { + sendData(ctx, conn, {type: 'unSaveLock', index, time, syncChangesIndex}); + } else { + ctx.logger.warn("unSaveLock failure"); } - sendData(conn, {type: "message", messages: allMessagesParsed}); } - function* _checkLock(docId, arrayBlocks) { - // Data is array now - var isLock = false; - var allLocks = yield* getAllLocks(docId); - var documentLocks = {}; - for(var i = 0 ; i < allLocks.length; ++i) { - var elem = allLocks[i]; - documentLocks[elem.block] =elem; - } - if (arrayBlocks.length > 0) { - for (var i = 0; i < arrayBlocks.length; ++i) { - var block = arrayBlocks[i]; - logger.info("getLock id: docId = %s %s", docId, block); - if (documentLocks.hasOwnProperty(block) && documentLocks[block] !== null) { - isLock = true; - break; - } - } - } else { - isLock = true; - } - return {res: !isLock, documentLocks: documentLocks}; + // Returning all messages for a document + function* getMessages(ctx, conn) { + let allMessages = yield editorData.getMessages(ctx, conn.docId); + allMessages = allMessages.length > 0 ? allMessages : undefined;//todo client side + sendDataMessage(ctx, conn, allMessages); } - function* _checkLockExcel(docId, arrayBlocks, userId) { + function _checkLockWord(ctx, docId, documentLocks, newLocks, arrayBlocks, userId) { + return true; + } + function _checkLockExcel(ctx, docId, documentLocks, newLocks, arrayBlocks, userId) { // Data is array now var documentLock; var isLock = false; var isExistInArray = false; var i, blockRange; - var documentLocks = yield* getAllLocks(docId); var lengthArray = (arrayBlocks) ? arrayBlocks.length : 0; for (i = 0; i < lengthArray && false === isLock; ++i) { blockRange = arrayBlocks[i]; - for (var keyLockInArray in documentLocks) { - if (true === isLock) { - break; - } - if (!documentLocks.hasOwnProperty(keyLockInArray)) { + for (let keyLockInArray in documentLocks) { + if (newLocks[keyLockInArray]) { + //skip just added continue; } documentLock = documentLocks[keyLockInArray]; - // Проверка Ð²Ñ…Ð¾Ð¶Ð´ÐµÐ½Ð¸Ñ Ð¾Ð±ÑŠÐµÐºÑ‚Ð° в маÑÑив (текущий пользователь еще раз приÑлал lock) + // Checking if an object is in an array (the current user sent a lock again) if (documentLock.user === userId && blockRange.sheetId === documentLock.block.sheetId && blockRange.type === c_oAscLockTypeElem.Object && @@ -2341,18 +3352,17 @@ exports.install = function(server, callbackFunction) { if (c_oAscLockTypeElem.Sheet === blockRange.type && c_oAscLockTypeElem.Sheet === documentLock.block.type) { - // ЕÑли текущий пользователь приÑлал lock текущего лиÑта, то не заноÑим в маÑÑив, а еÑли нового, то заноÑим + // If the current user sent a lock of the current sheet, then we do not enter it into the array, and if a new one, then we enter it if (documentLock.user === userId) { if (blockRange.sheetId === documentLock.block.sheetId) { - // уже еÑть в маÑÑиве isExistInArray = true; break; } else { - // новый лиÑÑ‚ + // new sheet continue; } } else { - // ЕÑли кто-то залочил sheet, то больше никто не может лочить sheet-Ñ‹ (иначе можно удалить вÑе лиÑты) + // If someone has locked a sheet, then no one else can lock sheets (otherwise you can delete all sheets) isLock = true; break; } @@ -2363,192 +3373,261 @@ exports.install = function(server, callbackFunction) { continue; } isLock = compareExcelBlock(blockRange, documentLock.block); + if (true === isLock) { + break; + } } } if (0 === lengthArray) { isLock = true; } - return {res: !isLock && !isExistInArray, documentLocks: documentLocks}; + return !isLock && !isExistInArray; } - function* _checkLockPresentation(docId, arrayBlocks, userId) { + function _checkLockPresentation(ctx, docId, documentLocks, newLocks, arrayBlocks, userId) { // Data is array now var isLock = false; - var i, documentLock, blockRange; - var documentLocks = yield* getAllLocks(docId); + var i, blockRange; var lengthArray = (arrayBlocks) ? arrayBlocks.length : 0; for (i = 0; i < lengthArray && false === isLock; ++i) { blockRange = arrayBlocks[i]; - for (var keyLockInArray in documentLocks) { - if (true === isLock) { - break; - } - if (!documentLocks.hasOwnProperty(keyLockInArray)) { + for (let keyLockInArray in documentLocks) { + if (newLocks[keyLockInArray]) { + //skip just added continue; } - documentLock = documentLocks[keyLockInArray]; - + let documentLock = documentLocks[keyLockInArray]; if (documentLock.user === userId || !(documentLock.block)) { continue; } isLock = comparePresentationBlock(blockRange, documentLock.block); + if (true === isLock) { + break; + } } } if (0 === lengthArray) { isLock = true; } - return {res: !isLock, documentLocks: documentLocks}; + return !isLock; } - function _checkLicense(conn) { - return co(function* () { - try { - const c_LR = constants.LICENSE_RESULT; - let licenseType = licenseInfo.type; - if (constants.PACKAGE_TYPE_OS === licenseInfo.packageType && c_LR.Error === licenseType) { - licenseType = c_LR.SuccessLimit; - - const count = constants.LICENSE_CONNECTIONS; - let cursor = '0', sum = 0, scanRes, tmp, length, i, users; - while (true) { - scanRes = yield utils.promiseRedis(redisClient, redisClient.scan, cursor, 'MATCH', redisKeyPresenceHash + '*'); - tmp = scanRes[1]; - sum += (length = tmp.length); - - for (i = 0; i < length; ++i) { - if (sum >= count) { - licenseType = c_LR.Connections; - break; - } - - users = yield utils.promiseRedis(redisClient, redisClient.hlen, tmp[i]); - sum += users - (0 !== users ? 1 : 0); - } - - if (sum >= count) { - licenseType = c_LR.Connections; - break; - } + function _checkLicense(ctx, conn) { + return co(function* () { + try { + ctx.logger.info('_checkLicense start'); + const tenEditSingleton = ctx.getCfg('services.CoAuthoring.server.edit_singleton', cfgEditSingleton); + const tenOpenProtectedFile = ctx.getCfg('services.CoAuthoring.server.openProtectedFile', cfgOpenProtectedFile); + const tenIsAnonymousSupport = ctx.getCfg('services.CoAuthoring.server.isAnonymousSupport', cfgIsAnonymousSupport); + + let rights = constants.RIGHTS.Edit; + if (tenEditSingleton) { + // ToDo docId from url ? + let handshake = conn.handshake; + const docIdParsed = constants.DOC_ID_SOCKET_PATTERN.exec(handshake.url); + if (docIdParsed && 1 < docIdParsed.length) { + const participantsMap = yield getParticipantMap(ctx, docIdParsed[1]); + for (let i = 0; i < participantsMap.length; ++i) { + const elem = participantsMap[i]; + if (!elem.view) { + rights = constants.RIGHTS.View; + break; + } + } + } + } + + let [licenseInfo] = yield tenantManager.getTenantLicense(ctx); + + sendData(ctx, conn, { + type: 'license', license: { + type: licenseInfo.type, + light: false,//todo remove in sdk + mode: licenseInfo.mode, + rights: rights, + buildVersion: commonDefines.buildVersion, + buildNumber: commonDefines.buildNumber, + protectionSupport: tenOpenProtectedFile, //todo find a better place + isAnonymousSupport: tenIsAnonymousSupport, //todo find a better place + liveViewerSupport: utils.isLiveViewerSupport(licenseInfo), + branding: licenseInfo.branding, + customization: licenseInfo.customization, + advancedApi: licenseInfo.advancedApi + } + }); + ctx.logger.info('_checkLicense end'); + } catch (err) { + ctx.logger.error('_checkLicense error: %s', err.stack); + } + }); + } - cursor = scanRes[0]; - if ('0' === cursor) { - break; - } + function* _checkLicenseAuth(ctx, licenseInfo, userId, isLiveViewer) { + const tenWarningLimitPercents = ctx.getCfg('license.warning_limit_percents', cfgWarningLimitPercents) / 100; + const tenNotificationRuleLicenseLimitEdit = ctx.getCfg(`notification.rules.licenseLimitEdit.template`, cfgNotificationRuleLicenseLimitEdit); + const tenNotificationRuleLicenseLimitLiveViewer = ctx.getCfg(`notification.rules.licenseLimitLiveViewer.template`, cfgNotificationRuleLicenseLimitLiveViewer); + const c_LR = constants.LICENSE_RESULT; + let licenseType = licenseInfo.type; + if (c_LR.Success === licenseType || c_LR.SuccessLimit === licenseType) { + let notificationLimit; + let notificationTemplate = tenNotificationRuleLicenseLimitEdit; + let notificationType = notificationTypes.LICENSE_LIMIT_EDIT; + let notificationPercent = 100; + if (licenseInfo.usersCount) { + const nowUTC = getLicenseNowUtc(); + notificationLimit = 'users'; + if(isLiveViewer) { + notificationTemplate = tenNotificationRuleLicenseLimitLiveViewer; + notificationType = notificationTypes.LICENSE_LIMIT_LIVE_VIEWER; + const arrUsers = yield editorStat.getPresenceUniqueViewUser(ctx, nowUTC); + if (arrUsers.length >= licenseInfo.usersViewCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { + licenseType = licenseInfo.hasLicense ? c_LR.UsersViewCount : c_LR.UsersViewCountOS; + } else if (licenseInfo.usersViewCount * tenWarningLimitPercents <= arrUsers.length) { + notificationPercent = tenWarningLimitPercents * 100; } - } - - let rights = constants.RIGHTS.Edit; - if (config.get('server.edit_singleton')) { - // ToDo docId from url ? - const docIdParsed = urlParse.exec(conn.url); - if (docIdParsed && 1 < docIdParsed.length) { - const participantsMap = yield* getParticipantMap(docIdParsed[1]); - for (let i = 0; i < participantsMap.length; ++i) { - const elem = participantsMap[i]; - if (!elem.view) { - rights = constants.RIGHTS.View; - break; - } - } + } else { + const arrUsers = yield editorStat.getPresenceUniqueUser(ctx, nowUTC); + if (arrUsers.length >= licenseInfo.usersCount && (-1 === arrUsers.findIndex((element) => {return element.userid === userId}))) { + licenseType = licenseInfo.hasLicense ? c_LR.UsersCount : c_LR.UsersCountOS; + } else if(licenseInfo.usersCount * tenWarningLimitPercents <= arrUsers.length) { + notificationPercent = tenWarningLimitPercents * 100; } } - - sendData(conn, { - type: 'license', - license: { - type: licenseType, - light: licenseInfo.light, - trial: constants.PACKAGE_TYPE_OS === licenseInfo.packageType ? false : licenseInfo.trial, - rights: rights, - buildVersion: commonDefines.buildVersion, - buildNumber: commonDefines.buildNumber, - branding: licenseInfo.branding + } else { + notificationLimit = 'connections'; + if (isLiveViewer) { + notificationTemplate = tenNotificationRuleLicenseLimitLiveViewer; + notificationType = notificationTypes.LICENSE_LIMIT_LIVE_VIEWER; + const connectionsLiveCount = licenseInfo.connectionsView; + const liveViewerConnectionsCount = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); + if (liveViewerConnectionsCount >= connectionsLiveCount) { + licenseType = licenseInfo.hasLicense ? c_LR.ConnectionsLive : c_LR.ConnectionsLiveOS; + } else if(connectionsLiveCount * tenWarningLimitPercents <= liveViewerConnectionsCount){ + notificationPercent = tenWarningLimitPercents * 100; } - }); - } catch (err) { - logger.error('_checkLicense error:\r\n%s', err.stack); + } else { + const connectionsCount = licenseInfo.connections; + const editConnectionsCount = yield editorStat.getEditorConnectionsCount(ctx, connections); + if (editConnectionsCount >= connectionsCount) { + licenseType = licenseInfo.hasLicense ? c_LR.Connections : c_LR.ConnectionsOS; + } else if (connectionsCount * tenWarningLimitPercents <= editConnectionsCount) { + notificationPercent = tenWarningLimitPercents * 100; + } + } } - }); + if ((c_LR.Success !== licenseType && c_LR.SuccessLimit !== licenseType) || 100 !== notificationPercent) { + const applicationName = (process.env.APPLICATION_NAME || "").toUpperCase(); + const title = util.format(notificationTemplate.title, applicationName); + const message = util.format(notificationTemplate.body, notificationPercent, notificationLimit); + if (100 !== notificationPercent) { + ctx.logger.warn(message); + } else { + ctx.logger.error(message); + } + //todo with yield service could throw error + void notificationService.notify(ctx, notificationType, title, message, notificationType + notificationPercent); + } + } + return licenseType; } - sockjs_echo.installHandlers(server, {prefix: '/doc/['+constants.DOC_ID_PATTERN+']*/c', log: function(severity, message) { - //TODO: handle severity - logger.info(message); - }}); - //publish subscribe message brocker - function pubsubOnMessage(msg) { + pubsubOnMessage = function(msg) { return co(function* () { + let ctx = new operationContext.Context(); try { - logger.debug('pubsub message start:%s', msg); var data = JSON.parse(msg); + ctx.initFromPubSub(data); + yield ctx.initTenantCache(); + ctx.logger.debug('pubsub message start:%s', msg); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + var participants; var participant; var objChangesDocument; var i; + let lockDocumentTimer, cmd; switch (data.type) { case commonDefines.c_oPublishType.drop: - for (i = 0; i < data.users.length; ++i) { - dropUserFromDocument(data.docId, data.users[i], data.description); - } + dropUserFromDocument(ctx, data.docId, data.users, data.description); + break; + case commonDefines.c_oPublishType.closeConnection: + closeUsersConnection(ctx, data.docId, data.usersMap, data.isOriginalId, data.code, data.description); break; case commonDefines.c_oPublishType.releaseLock: participants = getParticipants(data.docId, true, data.userId, true); _.each(participants, function(participant) { - sendReleaseLock(participant, data.locks); + sendReleaseLock(ctx, participant, data.locks); }); break; case commonDefines.c_oPublishType.participantsState: - participants = getParticipants(data.docId, true, data.user.id); - sendParticipantsState(participants, data); + participants = getParticipants(data.docId, true, data.userId); + sendParticipantsState(ctx, participants, data); break; case commonDefines.c_oPublishType.message: participants = getParticipants(data.docId, true, data.userId); _.each(participants, function(participant) { - sendDataMessage(participant, data.messages); + sendDataMessage(ctx, participant, data.messages); }); break; case commonDefines.c_oPublishType.getLock: participants = getParticipants(data.docId, true, data.userId, true); - sendGetLock(participants, data.documentLocks); + sendGetLock(ctx, participants, data.documentLocks); break; case commonDefines.c_oPublishType.changes: - participants = getParticipants(data.docId, true, data.userId, true); + lockDocumentTimer = lockDocumentsTimerId[data.docId]; + if (lockDocumentTimer) { + ctx.logger.debug("lockDocumentsTimerId update c_oPublishType.changes"); + cleanLockDocumentTimer(data.docId, lockDocumentTimer); + yield* setLockDocumentTimer(ctx, data.docId, lockDocumentTimer.userId); + } + participants = getParticipants(data.docId, true, data.userId); if(participants.length > 0) { var changes = data.changes; if (null == changes) { - objChangesDocument = yield* getDocumentChanges(data.docId, data.startIndex, data.changesIndex); + objChangesDocument = yield* getDocumentChanges(ctx, data.docId, data.startIndex, data.changesIndex); changes = objChangesDocument.arrChanges; } _.each(participants, function(participant) { - sendData(participant, {type: 'saveChanges', changes: changes, - changesIndex: data.changesIndex, locks: data.locks, excelAdditionalInfo: data.excelAdditionalInfo}); + if (!needSendChanges(participant)) { + return; + } + sendData(ctx, participant, {type: 'saveChanges', changes: changes, + changesIndex: data.changesIndex, syncChangesIndex: data.syncChangesIndex, endSaveChanges: data.endSaveChanges, + locks: data.locks, excelAdditionalInfo: data.excelAdditionalInfo}); }); } break; + case commonDefines.c_oPublishType.changesNotify: + lockDocumentTimer = lockDocumentsTimerId[data.docId]; + if (lockDocumentTimer) { + ctx.logger.debug("lockDocumentsTimerId update c_oPublishType.changesNotify"); + cleanLockDocumentTimer(data.docId, lockDocumentTimer); + yield* setLockDocumentTimer(ctx, data.docId, lockDocumentTimer.userId); + } + break; case commonDefines.c_oPublishType.auth: + lockDocumentTimer = lockDocumentsTimerId[data.docId]; + if (lockDocumentTimer) { + ctx.logger.debug("lockDocumentsTimerId clear"); + cleanLockDocumentTimer(data.docId, lockDocumentTimer); + } participants = getParticipants(data.docId, true, data.userId, true); if(participants.length > 0) { - objChangesDocument = yield* getDocumentChanges(data.docId); + yield* sendAuthChanges(ctx, data.docId, participants); for (i = 0; i < participants.length; ++i) { participant = participants[i]; - yield* sendAuthInfo(objChangesDocument.arrChanges, objChangesDocument.getLength(), participant, data.participantsMap); + yield* sendAuthInfo(ctx, participant, false, data.participantsMap); } } break; case commonDefines.c_oPublishType.receiveTask: - var cmd = new commonDefines.InputCommand(data.cmd); + cmd = new commonDefines.InputCommand(data.cmd, true); var output = new canvasService.OutputDataWrap(); output.fromObject(data.output); var outputData = output.getData(); - var docConnectionId = cmd.getDocConnectionId(); - var docId; - if(docConnectionId){ - docId = docConnectionId; - } else { - docId = cmd.getDocId(); - } + var docId = cmd.getDocId(); if (cmd.getUserConnectionId()) { participants = getParticipantUser(docId, cmd.getUserConnectionId()); } else { @@ -2557,301 +3636,799 @@ exports.install = function(server, callbackFunction) { for (i = 0; i < participants.length; ++i) { participant = participants[i]; if (data.needUrlKey) { - if (0 == data.needUrlMethod) { - outputData.setData(yield storage.getSignedUrls(participant.baseUrl, data.needUrlKey)); - } else if (1 == data.needUrlMethod) { - outputData.setData(yield storage.getSignedUrl(participant.baseUrl, data.needUrlKey)); + if (0 === data.needUrlMethod) { + outputData.setData(yield storage.getSignedUrls(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, data.creationDate)); + } else if (1 === data.needUrlMethod) { + outputData.setData(yield storage.getSignedUrl(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, undefined, data.creationDate)); } else { - var contentDisposition = cmd.getInline() ? constants.CONTENT_DISPOSITION_INLINE : constants.CONTENT_DISPOSITION_ATTACHMENT; - outputData.setData(yield storage.getSignedUrl(participant.baseUrl, data.needUrlKey, null, cmd.getTitle(), contentDisposition)); + let url; + if (cmd.getInline()) { + url = yield canvasService.getPrintFileUrl(ctx, data.needUrlKey, participant.baseUrl, cmd.getTitle()); + outputData.setExtName('.pdf'); + } else { + url = yield storage.getSignedUrl(ctx, participant.baseUrl, data.needUrlKey, data.needUrlType, cmd.getTitle(), data.creationDate); + outputData.setExtName(pathModule.extname(data.needUrlKey)); + } + outputData.setData(url); } + if (undefined !== data.openedAt) { + outputData.setOpenedAt(data.openedAt); + } + yield modifyConnectionForPassword(ctx, participant, data.needUrlIsCorrectPassword); } - sendData(participant, output); + sendData(ctx, participant, output); } break; case commonDefines.c_oPublishType.warning: participants = getParticipants(data.docId); _.each(participants, function(participant) { - sendDataWarning(participant, data.description); + sendDataWarning(ctx, participant, data.description); }); break; case commonDefines.c_oPublishType.cursor: participants = getParticipants(data.docId, true, data.userId); _.each(participants, function(participant) { - sendDataCursor(participant, data.messages); + sendDataCursor(ctx, participant, data.messages); }); break; case commonDefines.c_oPublishType.shutdown: - logger.debug('start shutdown'); //flag prevent new socket connections and receive data from exist connections - shutdownFlag = true; - logger.debug('active connections: %d', connections.length); - //не оÑтанавливаем Ñервер, Ñ‚.к. будут недоÑтупны Ñокеты и вÑе запроÑÑ‹ - //плохо тем, что может понадобитÑÑ ÐºÐ¾Ð½Ð²ÐµÑ€Ñ‚Ð°Ñ†Ð¸Ñ Ð²Ñ‹Ñ…Ð¾Ð´Ð½Ð¾Ð³Ð¾ файла и то что не будут обработаны запроÑÑ‹ на CommandService - //server.close(); - //in the cycle we will remove elements so copy array - var connectionsTmp = connections.slice(); - //destroy all open connections - for (i = 0; i < connectionsTmp.length; ++i) { - connectionsTmp[i].close(constants.SHUTDOWN_CODE, constants.SHUTDOWN_REASON); + shutdownFlag = data.status; + wopiClient.setIsShutdown(shutdownFlag); + ctx.logger.warn('start shutdown:%s', shutdownFlag); + if (shutdownFlag) { + ctx.logger.warn('active connections: %d', connections.length); + //do not stop the server, because sockets and all requests will be unavailable + //bad because you may need to convert the output file and the fact that requests for the CommandService will not be processed + //server.close(); + //in the cycle we will remove elements so copy array + var connectionsTmp = connections.slice(); + //destroy all open connections + for (i = 0; i < connectionsTmp.length; ++i) { + sendDataDisconnectReason(ctx, connectionsTmp[i], constants.SHUTDOWN_CODE, constants.SHUTDOWN_REASON); + connectionsTmp[i].disconnect(true); + } } - logger.debug('end shutdown'); + ctx.logger.warn('end shutdown'); break; case commonDefines.c_oPublishType.meta: participants = getParticipants(data.docId); _.each(participants, function(participant) { - sendDataMeta(participant, data.meta); + sendDataMeta(ctx, participant, data.meta); }); break; case commonDefines.c_oPublishType.forceSave: participants = getParticipants(data.docId, true, data.userId, true); _.each(participants, function(participant) { - sendData(participant, {type: "forceSave", messages: data.data}); + sendData(ctx, participant, {type: "forceSave", messages: data.data}); + }); + break; + case commonDefines.c_oPublishType.changeConnecitonInfo: + let hasChanges = false; + cmd = new commonDefines.InputCommand(data.cmd, true); + participants = getParticipants(data.docId); + for (i = 0; i < participants.length; ++i) { + participant = participants[i]; + if (!participant.denyChangeName && participant.user.idOriginal === data.useridoriginal) { + hasChanges = true; + ctx.logger.debug('changeConnectionInfo: userId = %s', data.useridoriginal); + participant.user.username = cmd.getUserName(); + yield addPresence(ctx, participant, false); + if (tenTokenEnableBrowser) { + let sessionToken = yield fillJwtByConnection(ctx, participant); + sendDataRefreshToken(ctx, participant, sessionToken); + } + } + } + if (hasChanges) { + let participants = yield getParticipantMap(ctx, data.docId); + let participantsTimestamp = Date.now(); + yield publish(ctx, {type: commonDefines.c_oPublishType.participantsState, ctx: ctx, docId: data.docId, userId: null, participantsTimestamp: participantsTimestamp, participants: participants}); + } + break; + case commonDefines.c_oPublishType.rpc: + participants = getParticipantUser(data.docId, data.userId); + _.each(participants, function(participant) { + sendDataRpc(ctx, participant, data.responseKey, data.data); }); break; default: - logger.debug('pubsub unknown message type:%s', msg); + ctx.logger.debug('pubsub unknown message type:%s', msg); } } catch (err) { - logger.error('pubsub message error:\r\n%s', err.stack); + ctx.logger.error('pubsub message error: %s', err.stack); } }); } + + function* collectStats(ctx, countEdit, countLiveView, countView) { + let now = Date.now(); + yield editorStat.setEditorConnections(ctx, countEdit, countLiveView, countView, now, PRECISION); + } function expireDoc() { - var cronJob = this; return co(function* () { + let ctx = new operationContext.Context(); try { - var countEdit = 0; - var countView = 0; - logger.debug('expireDoc connections.length = %d', connections.length); - var commands = []; - var idSet = new Set(); + let tenants = {}; + let countEditByShard = 0; + let countLiveViewByShard = 0; + let countViewByShard = 0; + ctx.logger.debug('expireDoc connections.length = %d', connections.length); var nowMs = new Date().getTime(); - var nextMs = cronJob.nextDate(); - var maxMs = Math.max(nowMs + cfgExpSessionCloseCommand, nextMs); for (var i = 0; i < connections.length; ++i) { var conn = connections[i]; - if (cfgExpSessionAbsolute > 0) { - if (maxMs - conn.sessionTimeConnect > cfgExpSessionAbsolute && !conn.sessionIsSendWarning) { + ctx.initFromConnection(conn); + //todo group by tenant + yield ctx.initTenantCache(); + const tenExpSessionIdle = ms(ctx.getCfg('services.CoAuthoring.expire.sessionidle', cfgExpSessionIdle)); + const tenExpSessionAbsolute = ms(ctx.getCfg('services.CoAuthoring.expire.sessionabsolute', cfgExpSessionAbsolute)); + const tenExpSessionCloseCommand = ms(ctx.getCfg('services.CoAuthoring.expire.sessionclosecommand', cfgExpSessionCloseCommand)); + + let maxMs = nowMs + Math.max(tenExpSessionCloseCommand, expDocumentsStep); + let tenant = tenants[ctx.tenant]; + if (!tenant) { + tenant = tenants[ctx.tenant] = {countEditByShard: 0, countLiveViewByShard: 0, countViewByShard: 0}; + } + //wopi access_token_ttl; + if (tenExpSessionAbsolute > 0 || conn.access_token_ttl) { + if ((tenExpSessionAbsolute > 0 && maxMs - conn.sessionTimeConnect > tenExpSessionAbsolute || + (conn.access_token_ttl && maxMs > conn.access_token_ttl)) && !conn.sessionIsSendWarning) { conn.sessionIsSendWarning = true; - sendDataSession(conn, { + sendDataSession(ctx, conn, { code: constants.SESSION_ABSOLUTE_CODE, reason: constants.SESSION_ABSOLUTE_REASON }); - } else if (nowMs - conn.sessionTimeConnect > cfgExpSessionAbsolute) { - conn.close(constants.SESSION_ABSOLUTE_CODE, constants.SESSION_ABSOLUTE_REASON); + } else if (nowMs - conn.sessionTimeConnect > tenExpSessionAbsolute) { + ctx.logger.debug('expireDoc close absolute session'); + sendDataDisconnectReason(ctx, conn, constants.SESSION_ABSOLUTE_CODE, constants.SESSION_ABSOLUTE_REASON); + conn.disconnect(true); continue; } } - if (cfgExpSessionIdle > 0) { - if (maxMs - conn.sessionTimeLastAction > cfgExpSessionIdle && !conn.sessionIsSendWarning) { + if (tenExpSessionIdle > 0 && !(conn.user?.view || conn.isCloseCoAuthoring)) { + if (maxMs - conn.sessionTimeLastAction > tenExpSessionIdle && !conn.sessionIsSendWarning) { conn.sessionIsSendWarning = true; - sendDataSession(conn, { + sendDataSession(ctx, conn, { code: constants.SESSION_IDLE_CODE, reason: constants.SESSION_IDLE_REASON, - interval: cfgExpSessionIdle + interval: tenExpSessionIdle }); - } else if (nowMs - conn.sessionTimeLastAction > cfgExpSessionIdle) { - conn.close(constants.SESSION_IDLE_CODE, constants.SESSION_IDLE_REASON); + } else if (nowMs - conn.sessionTimeLastAction > tenExpSessionIdle) { + ctx.logger.debug('expireDoc close idle session'); + sendDataDisconnectReason(ctx, conn, constants.SESSION_IDLE_CODE, constants.SESSION_IDLE_REASON); + conn.disconnect(true); continue; } } - if (constants.CONN_CLOSED === conn.readyState) { - logger.error('expireDoc connection closed docId = %s', conn.docId); + if (constants.CONN_CLOSED === conn.conn.readyState) { + ctx.logger.error('expireDoc connection closed'); } - idSet.add(conn.docId); - updatePresenceCommandsToArray(commands, conn.docId, conn.user.id, getConnectionInfo(conn)); - if (conn.user && conn.user.view) { - countView++; + yield updatePresence(ctx, conn); + if (utils.isLiveViewer(conn)) { + countLiveViewByShard++; + tenant.countLiveViewByShard++; + } else if(conn.isCloseCoAuthoring || (conn.user && conn.user.view)) { + countViewByShard++; + tenant.countViewByShard++; } else { - countEdit++; + countEditByShard++; + tenant.countEditByShard++; } } - var expireAt = new Date().getTime() + cfgExpPresence * 1000; - idSet.forEach(function(value1, value2, set) { - commands.push(['zadd', redisKeyDocuments, expireAt, value1]); - }); - if (commands.length > 0) { - var multi = redisClient.multi(commands); - yield utils.promiseRedis(multi, multi.exec); + for (let tenantId in tenants) { + if(tenants.hasOwnProperty(tenantId)) { + ctx.setTenant(tenantId); + let tenant = tenants[tenantId]; + yield* collectStats(ctx, tenant.countEditByShard, tenant.countLiveViewByShard, tenant.countViewByShard); + yield editorStat.setEditorConnectionsCountByShard(ctx, SHARD_ID, tenant.countEditByShard); + yield editorStat.setLiveViewerConnectionsCountByShard(ctx, SHARD_ID, tenant.countLiveViewByShard); + yield editorStat.setViewerConnectionsCountByShard(ctx, SHARD_ID, tenant.countViewByShard); + if (clientStatsD) { + //todo with multitenant + let countEdit = yield editorStat.getEditorConnectionsCount(ctx, connections); + clientStatsD.gauge('expireDoc.connections.edit', countEdit); + let countLiveView = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); + clientStatsD.gauge('expireDoc.connections.liveview', countLiveView); + let countView = yield editorStat.getViewerConnectionsCount(ctx, connections); + clientStatsD.gauge('expireDoc.connections.view', countView); + } + } } - if (clientStatsD) { - clientStatsD.gauge('expireDoc.connections.edit', countEdit); - clientStatsD.gauge('expireDoc.connections.view', countView); + if (tenantManager.isMultitenantMode(ctx) && !tenantManager.isDefaultTenant(ctx)) { + //aggregated tenant stats + let aggregationCtx = new operationContext.Context(); + aggregationCtx.init(tenantManager.getDefautTenant(), ctx.docId, ctx.userId); + //yield ctx.initTenantCache();//no need + yield* collectStats(aggregationCtx, countEditByShard, countLiveViewByShard, countViewByShard); + yield editorStat.setEditorConnectionsCountByShard(aggregationCtx, SHARD_ID, countEditByShard); + yield editorStat.setLiveViewerConnectionsCountByShard(aggregationCtx, SHARD_ID, countLiveViewByShard); + yield editorStat.setViewerConnectionsCountByShard(aggregationCtx, SHARD_ID, countViewByShard); } + ctx.initDefault(); } catch (err) { - logger.error('expireDoc error:\r\n%s', err.stack); + ctx.logger.error('expireDoc error: %s', err.stack); + } finally { + setTimeout(expireDoc, expDocumentsStep); } }); } - var innerPingJob = function(opt_isStart) { - if (!opt_isStart) { - logger.warn('expireDoc restart'); - } - new cron.CronJob(cfgExpDocumentsCron, expireDoc, innerPingJob, true); - }; - innerPingJob(true); + setTimeout(expireDoc, expDocumentsStep); + function refreshWopiLock() { + return co(function* () { + let ctx = new operationContext.Context(); + try { + ctx.logger.info('refreshWopiLock start'); + let docIds = new Map(); + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + ctx.initFromConnection(conn); + //todo group by tenant + yield ctx.initTenantCache(); + let docId = conn.docId; + if ((conn.user && conn.user.view) || docIds.has(docId)) { + continue; + } + docIds.set(docId, 1); + if (undefined === conn.access_token_ttl) { + continue; + } + let selectRes = yield taskResult.select(ctx, docId); + if (selectRes.length > 0 && selectRes[0] && selectRes[0].callback) { + let callback = selectRes[0].callback; + let callbackUrl = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, callback); + let wopiParams = wopiClient.parseWopiCallback(ctx, callbackUrl, callback); + if (wopiParams && wopiParams.commonInfo) { + yield wopiClient.lock(ctx, 'REFRESH_LOCK', wopiParams.commonInfo.lockId, + wopiParams.commonInfo.fileInfo, wopiParams.userAuth); + } + } + } + ctx.initDefault(); + ctx.logger.info('refreshWopiLock end'); + } catch (err) { + ctx.logger.error('refreshWopiLock error:%s', err.stack); + } finally { + setTimeout(refreshWopiLock, cfgRefreshLockInterval); + } + }); + } + setTimeout(refreshWopiLock, cfgRefreshLockInterval); pubsub = new pubsubService(); pubsub.on('message', pubsubOnMessage); pubsub.init(function(err) { if (null != err) { - logger.error('createPubSub error :\r\n%s', err.stack); + operationContext.global.logger.error('createPubSub error: %s', err.stack); } queue = new queueService(); queue.on('dead', handleDeadLetter); queue.on('response', canvasService.receiveTask); - queue.init(true, false, false, true, function(err){ + queue.init(true, true, false, true, true, true, function(err){ if (null != err) { - logger.error('createTaskQueue error :\r\n%s', err.stack); + operationContext.global.logger.error('createTaskQueue error: %s', err.stack); } - - callbackFunction(); + gc.startGC(); + + //check data base compatibility + const tables = [ + [cfgTableResult, constants.TABLE_RESULT_SCHEMA], + [cfgTableChanges, constants.TABLE_CHANGES_SCHEMA] + ]; + const requestPromises = tables.map(table => isSchemaCompatible(table)); + + Promise.all(requestPromises).then( + checkResult => { + if (checkResult.includes(false)) { + return; + } + editorData + .connect() + .then(() => editorStat.connect()) + .then(() => callbackFunction()) + .catch(err => { + operationContext.global.logger.error('editorData error: %s', err.stack); + }); + }, + error => operationContext.global.logger.error('getTableColumns error: %s', error.stack) + ); }); }); }; -exports.setLicenseInfo = function(data) { - licenseInfo = data; +exports.setLicenseInfo = async function(globalCtx, data, original) { + tenantManager.setDefLicense(data, original); + + await utilsDocService.notifyLicenseExpiration(globalCtx, data.endDate); + + const tenantsList = await tenantManager.getAllTenants(globalCtx); + for (const tenant of tenantsList) { + let ctx = new operationContext.Context(); + ctx.setTenant(tenant); + await ctx.initTenantCache(); + + const [licenseInfo] = await tenantManager.getTenantLicense(ctx); + await utilsDocService.notifyLicenseExpiration(ctx, licenseInfo.endDate); + } }; exports.healthCheck = function(req, res) { return co(function*() { let output = false; + let ctx = new operationContext.Context(); try { - logger.debug('healthCheck start'); - let promises = []; + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('healthCheck start'); //database - promises.push(sqlBase.healthCheck()); - //redis - promises.push(utils.promiseRedis(redisClient, redisClient.ping)); - yield Promise.all(promises); - //rabbitMQ - let conn = yield rabbitMQCore.connetPromise(function() {}); - yield rabbitMQCore.closePromise(conn); + yield sqlBase.healthCheck(ctx); + ctx.logger.debug('healthCheck database'); + //check redis connection + const healthData = yield editorData.healthCheck(); + if (healthData) { + ctx.logger.debug('healthCheck editorData'); + } else { + throw new Error('editorData'); + } + const healthStat = yield editorStat.healthCheck(); + if (healthStat) { + ctx.logger.debug('healthCheck editorStat'); + } else { + throw new Error('editorStat'); + } + const healthPubsub = yield pubsub.healthCheck(); + if (healthPubsub) { + ctx.logger.debug('healthCheck pubsub'); + } else { + throw new Error('pubsub'); + } + const healthQueue = yield queue.healthCheck(); + if (healthQueue) { + ctx.logger.debug('healthCheck queue'); + } else { + throw new Error('queue'); + } + //storage - const clusterId = cluster.isWorker ? cluster.worker.id : ''; - const tempName = 'hc_' + os.hostname() + '_' + clusterId + '_' + Math.round(Math.random() * HEALTH_CHECK_KEY_MAX); - const tempBuffer = new Buffer([1, 2, 3, 4, 5]); - //It's proper to putObject one tempName - yield storage.putObject(tempName, tempBuffer, tempBuffer.length); - try { - //try to prevent case, when another process can remove same tempName - yield storage.deleteObject(tempName); - } catch (err) { - logger.warn('healthCheck error\r\n%s', err.stack); + yield storage.healthCheck(ctx); + ctx.logger.debug('healthCheck storage'); + if (storage.isDiffrentPersistentStorage()) { + yield storage.healthCheck(ctx, cfgForgottenFiles); + ctx.logger.debug('healthCheck storage persistent'); } output = true; - logger.debug('healthCheck end'); + ctx.logger.info('healthCheck end'); } catch (err) { - logger.error('healthCheck error\r\n%s', err.stack); + ctx.logger.error('healthCheck error %s', err.stack); } finally { + res.setHeader('Content-Type', 'text/plain'); res.send(output.toString()); } }); }; -// Команда Ñ Ñервера (в чаÑтноÑти teamlab) -exports.commandFromServer = function (req, res) { - return co(function* () { - let result = commonDefines.c_oAscServerCommandErrors.NoError; - let docId = 'commandFromServer'; - let version = undefined; +exports.licenseInfo = function(req, res) { + return co(function*() { + let isError = false; + let serverDate = new Date(); + //security risk of high-precision time + serverDate.setMilliseconds(0); + let output = { + connectionsStat: {}, licenseInfo: {}, serverInfo: { + buildVersion: commonDefines.buildVersion, buildNumber: commonDefines.buildNumber, date: serverDate.toISOString() + }, quota: { + edit: { + connectionsCount: 0, + usersCount: { + unique: 0, + anonymous: 0, + } + }, + view: { + connectionsCount: 0, + usersCount: { + unique: 0, + anonymous: 0, + } + }, + byMonth: [] + } + }; + + let ctx = new operationContext.Context(); try { - let params; - if (req.body && Buffer.isBuffer(req.body)) { - params = JSON.parse(req.body.toString('utf8')); - } else { - params = req.query; - } - if (cfgTokenEnableRequestInbox) { - result = commonDefines.c_oAscServerCommandErrors.Token; - const checkJwtRes = checkJwtHeader(docId, req); - if (checkJwtRes) { - if (checkJwtRes.decoded) { - if (!utils.isEmptyObject(checkJwtRes.decoded.payload)) { - Object.assign(params, checkJwtRes.decoded.payload); - result = commonDefines.c_oAscServerCommandErrors.NoError; - } else if (checkJwtRes.decoded.payloadhash) { - if (checkJwtPayloadHash(docId, checkJwtRes.decoded.payloadhash, req.body, checkJwtRes.token)) { - result = commonDefines.c_oAscServerCommandErrors.NoError; - } - } else if (!utils.isEmptyObject(checkJwtRes.decoded.query)) { - Object.assign(params, checkJwtRes.decoded.query); - result = commonDefines.c_oAscServerCommandErrors.NoError; + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.debug('licenseInfo start'); + + let [licenseInfo] = yield tenantManager.getTenantLicense(ctx); + Object.assign(output.licenseInfo, licenseInfo); + + var precisionSum = {}; + for (let i = 0; i < PRECISION.length; ++i) { + precisionSum[PRECISION[i].name] = { + edit: {min: Number.MAX_VALUE, sum: 0, count: 0, intervalsInPresision: PRECISION[i].val / expDocumentsStep, max: 0}, + liveview: {min: Number.MAX_VALUE, sum: 0, count: 0, intervalsInPresision: PRECISION[i].val / expDocumentsStep, max: 0}, + view: {min: Number.MAX_VALUE, sum: 0, count: 0, intervalsInPresision: PRECISION[i].val / expDocumentsStep, max: 0} + }; + output.connectionsStat[PRECISION[i].name] = { + edit: {min: 0, avr: 0, max: 0}, + liveview: {min: 0, avr: 0, max: 0}, + view: {min: 0, avr: 0, max: 0} + }; + } + var redisRes = yield editorStat.getEditorConnections(ctx); + const now = Date.now(); + if (redisRes.length > 0) { + let expDocumentsStep95 = expDocumentsStep * 0.95; + let prevTime = Number.MAX_VALUE; + var precisionIndex = 0; + for (let i = redisRes.length - 1; i >= 0; i--) { + let elem = redisRes[i]; + let edit = elem.edit || 0; + let view = elem.view || 0; + let liveview = elem.liveview || 0; + //for cluster + while (i > 0 && elem.time - redisRes[i - 1].time < expDocumentsStep95) { + edit += elem.edit || 0; + view += elem.view || 0; + liveview += elem.liveview || 0; + i--; + } + for (let j = precisionIndex; j < PRECISION.length; ++j) { + if (now - elem.time < PRECISION[j].val) { + let precision = precisionSum[PRECISION[j].name]; + precision.edit.min = Math.min(precision.edit.min, edit); + precision.edit.max = Math.max(precision.edit.max, edit); + precision.edit.sum += edit + precision.edit.count++; + precision.view.min = Math.min(precision.view.min, view); + precision.view.max = Math.max(precision.view.max, view); + precision.view.sum += view; + precision.view.count++; + precision.liveview.min = Math.min(precision.liveview.min, liveview); + precision.liveview.max = Math.max(precision.liveview.max, liveview); + precision.liveview.sum += liveview; + precision.liveview.count++; + } else { + precisionIndex = j + 1; } + } + prevTime = elem.time; + } + for (let i in precisionSum) { + let precision = precisionSum[i]; + let precisionOut = output.connectionsStat[i]; + if (precision.edit.count > 0) { + precisionOut.edit.avr = Math.round(precision.edit.sum / precision.edit.intervalsInPresision); + precisionOut.edit.min = precision.edit.min; + precisionOut.edit.max = precision.edit.max; + } + if (precision.liveview.count > 0) { + precisionOut.liveview.avr = Math.round(precision.liveview.sum / precision.liveview.intervalsInPresision); + precisionOut.liveview.min = precision.liveview.min; + precisionOut.liveview.max = precision.liveview.max; + } + if (precision.view.count > 0) { + precisionOut.view.avr = Math.round(precision.view.sum / precision.view.intervalsInPresision); + precisionOut.view.min = precision.view.min; + precisionOut.view.max = precision.view.max; + } + } + } + const nowUTC = getLicenseNowUtc(); + let execRes; + execRes = yield editorStat.getPresenceUniqueUser(ctx, nowUTC); + output.quota.edit.connectionsCount = yield editorStat.getEditorConnectionsCount(ctx, connections); + output.quota.edit.usersCount.unique = execRes.length; + execRes.forEach(function(elem) { + if (elem.anonym) { + output.quota.edit.usersCount.anonymous++; + } + }); + + execRes = yield editorStat.getPresenceUniqueViewUser(ctx, nowUTC); + output.quota.view.connectionsCount = yield editorStat.getLiveViewerConnectionsCount(ctx, connections); + output.quota.view.usersCount.unique = execRes.length; + execRes.forEach(function(elem) { + if (elem.anonym) { + output.quota.view.usersCount.anonymous++; + } + }); + + let byMonth = yield editorStat.getPresenceUniqueUsersOfMonth(ctx); + let byMonthView = yield editorStat.getPresenceUniqueViewUsersOfMonth(ctx); + let byMonthMerged = []; + for (let i in byMonth) { + if (byMonth.hasOwnProperty(i)) { + byMonthMerged[i] = {date: i, users: byMonth[i], usersView: {}}; + } + } + for (let i in byMonthView) { + if (byMonthView.hasOwnProperty(i)) { + if (byMonthMerged.hasOwnProperty(i)) { + byMonthMerged[i].usersView = byMonthView[i]; } else { - if (constants.JWT_EXPIRED_CODE == checkJwtRes.code) { - result = commonDefines.c_oAscServerCommandErrors.TokenExpire; - } + byMonthMerged[i] = {date: i, users: {}, usersView: byMonthView[i]}; } } } - // Ключ id-документа - docId = params.key; - if (commonDefines.c_oAscServerCommandErrors.NoError === result && null == docId && 'version' != params.c) { - result = commonDefines.c_oAscServerCommandErrors.DocumentIdError; - } else if(commonDefines.c_oAscServerCommandErrors.NoError === result) { - logger.debug('Start commandFromServer: docId = %s c = %s', docId, params.c); - switch (params.c) { - case 'info': - //If no files in the database means they have not been edited. - const selectRes = yield taskResult.select(docId); - if (selectRes.length > 0) { - result = yield* bindEvents(docId, params.callback, utils.getBaseUrlByRequest(req), undefined, params.userdata); - } else { - result = commonDefines.c_oAscServerCommandErrors.DocumentIdError; - } - break; - case 'drop': - if (params.userid) { - yield* publish({type: commonDefines.c_oPublishType.drop, docId: docId, users: [params.userid], description: params.description}); - } else if (params.users) { - const users = (typeof params.users === 'string') ? JSON.parse(params.users) : params.users; - yield* dropUsersFromDocument(docId, users); - } else { - result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; - } - break; - case 'saved': - // Результат от менеджера документов о ÑтатуÑе обработки ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ñ„Ð°Ð¹Ð»Ð° поÑле Ñборки - if ('1' !== params.status) { - //Ð·Ð°Ð¿Ñ€Ð¾Ñ saved выполнÑетÑÑ Ñинхронно, поÑтому заполнÑем переменную чтобы проверить ее поÑле sendServerRequest - yield utils.promiseRedis(redisClient, redisClient.setex, redisKeySaved + docId, cfgExpSaved, params.status); - logger.error('saved corrupted id = %s status = %s conv = %s', docId, params.status, params.conv); - } else { - logger.info('saved id = %s status = %s conv = %s', docId, params.status, params.conv); - } - break; - case 'forcesave': - let forceSaveRes = yield* startForceSave(docId, commonDefines.c_oAscForceSaveTypes.Command, params.userdata, utils.getBaseUrlByRequest(req)); - result = forceSaveRes.code; - break; - case 'meta': - if (params.meta) { - yield* publish({type: commonDefines.c_oPublishType.meta, docId: docId, meta: params.meta}); - } else { - result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; - } - break; - case 'version': - version = commonDefines.buildVersion + '.' + commonDefines.buildNumber; - break; - default: - result = commonDefines.c_oAscServerCommandErrors.UnknownCommand; - break; + output.quota.byMonth = Object.values(byMonthMerged); + output.quota.byMonth.sort((a, b) => { + return a.date.localeCompare(b.date); + }); + + ctx.logger.debug('licenseInfo end'); + } catch (err) { + isError = true; + ctx.logger.error('licenseInfo error %s', err.stack); + } finally { + if (!isError) { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(output)); + } else { + res.sendStatus(400); + } + } + }); +}; +function validateInputParams(ctx, authRes, command) { + const commandsWithoutKey = ['version', 'license', 'getForgottenList']; + const isValidWithoutKey = commandsWithoutKey.includes(command.c); + const isDocIdString = typeof command.key === 'string'; + + ctx.setDocId(command.key); + + if(authRes.code === constants.VKEY_KEY_EXPIRE){ + return commonDefines.c_oAscServerCommandErrors.TokenExpire; + } else if(authRes.code !== constants.NO_ERROR){ + return commonDefines.c_oAscServerCommandErrors.Token; + } + + if (isValidWithoutKey || isDocIdString) { + return commonDefines.c_oAscServerCommandErrors.NoError; + } else { + return commonDefines.c_oAscServerCommandErrors.DocumentIdError; + } +} + +function* getFilesKeys(ctx, opt_specialDir) { + const directoryList = yield storage.listObjects(ctx, '', opt_specialDir); + const keys = directoryList.map(directory => directory.split('/')[0]); + + const filteredKeys = []; + let previousKey = null; + // Key is a folder name. This folder could consist of several files, which leads to N same strings in "keys" array in a row. + for (const key of keys) { + if (previousKey !== key) { + previousKey = key; + filteredKeys.push(key); + } + } + + return filteredKeys; +} + +function* findForgottenFile(ctx, docId) { + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + const tenForgottenFilesName = ctx.getCfg('services.CoAuthoring.server.forgottenfilesname', cfgForgottenFilesName); + + const forgottenList = yield storage.listObjects(ctx, docId, tenForgottenFiles); + return forgottenList.find(forgotten => tenForgottenFilesName === pathModule.basename(forgotten, pathModule.extname(forgotten))); +} + +function* commandLicense(ctx) { + const nowUTC = getLicenseNowUtc(); + const users = yield editorStat.getPresenceUniqueUser(ctx, nowUTC); + const users_view = yield editorStat.getPresenceUniqueViewUser(ctx, nowUTC); + const [licenseInfo, licenseOriginal] = yield tenantManager.getTenantLicense(ctx); + + return { + license: licenseOriginal || utils.convertLicenseInfoToFileParams(licenseInfo), + server: utils.convertLicenseInfoToServerParams(licenseInfo), + quota: { users, users_view } + }; +} + +async function proxyCommand(ctx, req, params) { + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + const tenTokenEnableRequestInbox = ctx.getCfg('services.CoAuthoring.token.enable.request.inbox', cfgTokenEnableRequestInbox); + //todo gen shardkey as in sdkjs + const shardkey = params.key; + const baseUrl = utils.getBaseUrlByRequest(ctx, req); + let url = `${baseUrl}/command?&${constants.SHARD_KEY_API_NAME}=${encodeURIComponent(shardkey)}`; + for (let name in req.query) { + url += `&${name}=${encodeURIComponent(req.query[name])}`; + } + ctx.logger.info('commandFromServer proxy request with "key" to correctly process commands in sharded cluster to url:%s', url); + //isInJwtToken is true because 'command' is always internal + return await utils.postRequestPromise(ctx, url, req.body, null, req.body.length, tenCallbackRequestTimeout, undefined, tenTokenEnableRequestInbox, req.headers); +} +/** + * Server commands handler. + * @param ctx Local context. + * @param params Request parameters. + * @param req Request object. + * @param output{{ key: string, error: number, version: undefined | string, users: [string]}}} Mutable. Response body. + * @returns undefined. + */ +function* commandHandle(ctx, params, req, output) { + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + + const docId = params.key; + const forgottenData = {}; + + switch (params.c) { + case 'info': { + //If no files in the database means they have not been edited. + const selectRes = yield taskResult.select(ctx, docId); + if (selectRes.length > 0) { + let sendData = yield* bindEvents(ctx, docId, params.callback, utils.getBaseUrlByRequest(ctx, req), undefined, params.userdata); + if (sendData) { + output.users = sendData.users || []; + } else { + output.error = commonDefines.c_oAscServerCommandErrors.ParseError; + } + } else { + output.error = commonDefines.c_oAscServerCommandErrors.DocumentIdError; + } + break; + } + case 'drop': { + if (params.users) { + const users = (typeof params.users === 'string') ? JSON.parse(params.users) : params.users; + yield dropUsersFromDocument(ctx, docId, users); + } else { + yield dropUsersFromDocument(ctx, docId); + } + break; + } + case 'saved': { + // Result from document manager about file save processing status after assembly + if ('1' !== params.status) { + //"saved" request is done synchronously so populate a variable to check it after sendServerRequest + yield editorData.setSaved(ctx, docId, params.status); + ctx.logger.warn('saved corrupted id = %s status = %s conv = %s', docId, params.status, params.conv); + } else { + ctx.logger.info('saved id = %s status = %s conv = %s', docId, params.status, params.conv); + } + break; + } + case 'forcesave': { + let forceSaveRes = yield startForceSave(ctx, docId, commonDefines.c_oAscForceSaveTypes.Command, params.userdata, undefined, undefined, undefined, undefined, undefined, undefined, utils.getBaseUrlByRequest(ctx, req)); + output.error = forceSaveRes.code; + break; + } + case 'meta': { + if (params.meta) { + yield publish(ctx, {type: commonDefines.c_oPublishType.meta, ctx: ctx, docId: docId, meta: params.meta}); + } else { + output.error = commonDefines.c_oAscServerCommandErrors.UnknownCommand; + } + break; + } + case 'getForgotten': { + // Checking for files existence. + const forgottenFileFullPath = yield* findForgottenFile(ctx, docId); + if (!forgottenFileFullPath) { + output.error = commonDefines.c_oAscServerCommandErrors.DocumentIdError; + break; + } + + const forgottenFile = pathModule.basename(forgottenFileFullPath); + + // Creating URLs from files. + const baseUrl = utils.getBaseUrlByRequest(ctx, req); + forgottenData.url = yield storage.getSignedUrl( + ctx, baseUrl, forgottenFileFullPath, commonDefines.c_oAscUrlTypes.Temporary, forgottenFile, undefined, tenForgottenFiles + ); + break; + } + case 'deleteForgotten': { + const forgottenFile = yield* findForgottenFile(ctx, docId); + if (!forgottenFile) { + output.error = commonDefines.c_oAscServerCommandErrors.DocumentIdError; + break; + } + + yield storage.deletePath(ctx, docId, tenForgottenFiles); + break; + } + case 'getForgottenList': { + forgottenData.keys = yield* getFilesKeys(ctx, tenForgottenFiles); + break; + } + case 'version': { + output.version = `${commonDefines.buildVersion}.${commonDefines.buildNumber}`; + break; + } + case 'license': { + const outputLicense = yield* commandLicense(ctx); + Object.assign(output, outputLicense); + break; + } + default: { + output.error = commonDefines.c_oAscServerCommandErrors.UnknownCommand; + break; + } + } + + Object.assign(output, forgottenData); +} + +// Command from the server (specifically teamlab) +exports.commandFromServer = function (req, res) { + return co(function* () { + const output = { key: 'commandFromServer', error: commonDefines.c_oAscServerCommandErrors.NoError, version: undefined, users: undefined}; + const ctx = new operationContext.Context(); + let postRes = null; + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('commandFromServer start'); + const authRes = yield getRequestParams(ctx, req); + const params = authRes.params; + // Key is document id + output.key = params.key; + output.error = validateInputParams(ctx, authRes, params); + if (output.error === commonDefines.c_oAscServerCommandErrors.NoError) { + if (params.key && !req.query[constants.SHARD_KEY_API_NAME] && !req.query[constants.SHARD_KEY_WOPI_NAME] && process.env.DEFAULT_SHARD_KEY) { + postRes = yield proxyCommand(ctx, req, params); + } else { + ctx.logger.debug('commandFromServer: c = %s', params.c); + yield* commandHandle(ctx, params, req, output); } } } catch (err) { - result = commonDefines.c_oAscServerCommandErrors.UnknownError; - logger.error('Error commandFromServer: docId = %s\r\n%s', docId, err.stack); + output.error = commonDefines.c_oAscServerCommandErrors.UnknownError; + ctx.logger.error('Error commandFromServer: %s', err.stack); } finally { - //undefined value are excluded in JSON.stringify - const output = JSON.stringify({'key': docId, 'error': result, 'version': version}); - logger.debug('End commandFromServer: docId = %s %s', docId, output); - const outputBuffer = new Buffer(output, 'utf8'); + let outputBuffer; + if (postRes) { + outputBuffer = postRes.body; + } else { + outputBuffer = Buffer.from(JSON.stringify(output), 'utf8'); + } res.setHeader('Content-Type', 'application/json'); res.setHeader('Content-Length', outputBuffer.length); res.send(outputBuffer); + ctx.logger.info('commandFromServer end : %s', outputBuffer); + } + }); +}; + +exports.shutdown = function(req, res) { + return co(function*() { + let output = false; + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('shutdown start'); + output = yield shutdown.shutdown(ctx, editorStat, req.method === 'PUT'); + } catch (err) { + ctx.logger.error('shutdown error %s', err.stack); + } finally { + res.setHeader('Content-Type', 'text/plain'); + res.send(output.toString()); + ctx.logger.info('shutdown end'); } }); }; +exports.getEditorConnectionsCount = function (req, res) { + let ctx = new operationContext.Context(); + let count = 0; + try { + ctx.initFromRequest(req); + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + if (!(conn.isCloseCoAuthoring || (conn.user && conn.user.view))) { + count++; + } + } + ctx.logger.info('getConnectionsCount count=%d', count); + } catch (err) { + ctx.logger.error('getConnectionsCount error %s', err.stack); + } finally { + res.setHeader('Content-Type', 'text/plain'); + res.send(count.toString()); + } +}; diff --git a/DocService/sources/baseConnector.js b/DocService/sources/baseConnector.js deleted file mode 100644 index 9e6232eb19..0000000000 --- a/DocService/sources/baseConnector.js +++ /dev/null @@ -1,288 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * Pursuant to Section 7(b) of the License you must retain the original Product - * logo when distributing the program. Pursuant to Section 7(e) we decline to - * grant you any rights under trademark law for use of our trademarks. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing 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 - * - */ - -'use strict'; - -var sqlDataBaseType = { - mySql : 'mysql', - postgreSql : 'postgres' -}; - -var config = require('config').get('services.CoAuthoring.sql'); -var baseConnector = (sqlDataBaseType.mySql === config.get('type')) ? require('./mySqlBaseConnector') : require('./postgreSqlBaseConnector'); - -var tableChanges = config.get('tableChanges'), - tableResult = config.get('tableResult'); - -var g_oCriticalSection = {}; -var maxPacketSize = config.get('max_allowed_packet'); // Размер по умолчанию Ð´Ð»Ñ Ð·Ð°Ð¿Ñ€Ð¾Ñа в базу данных 1Mb - 1 (Ñ‚.к. он не пишет 1048575, а пишет 1048574) - -function getDataFromTable (tableId, data, getCondition, callback) { - var table = getTableById(tableId); - var sqlCommand = "SELECT " + data + " FROM " + table + " WHERE " + getCondition + ";"; - - baseConnector.sqlQuery(sqlCommand, callback); -} -function deleteFromTable (tableId, deleteCondition, callback) { - var table = getTableById(tableId); - var sqlCommand = "DELETE FROM " + table + " WHERE " + deleteCondition + ";"; - - baseConnector.sqlQuery(sqlCommand, callback); -} -var c_oTableId = { - callbacks : 2, - changes : 3 -}; -function getTableById (id) { - var res; - switch (id) { - case c_oTableId.changes: - res = tableChanges; - break; - } - return res; -} - -exports.baseConnector = baseConnector; -exports.tableId = c_oTableId; -exports.loadTable = function (tableId, callbackFunction) { - var table = getTableById(tableId); - var sqlCommand = "SELECT * FROM " + table + ";"; - baseConnector.sqlQuery(sqlCommand, callbackFunction); -}; -exports.insertChanges = function (objChanges, docId, index, user) { - lockCriticalSection(docId, function () {_insertChanges(0, objChanges, docId, index, user);}); -}; -exports.insertChangesPromise = function (objChanges, docId, index, user) { - return new Promise(function(resolve, reject) { - _insertChangesCallback(0, objChanges, docId, index, user, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; -function _lengthInUtf8Bytes (s) { - return ~-encodeURI(s).split(/%..|./).length; -} -function _getDateTime2(oDate) { - return oDate.toISOString().slice(0, 19).replace('T', ' '); -} -function _getDateTime(nTime) { - var oDate = new Date(nTime); - return _getDateTime2(oDate); -} - -exports.getDateTime = _getDateTime2; -function _insertChanges (startIndex, objChanges, docId, index, user) { - _insertChangesCallback(startIndex, objChanges, docId, index, user, function () {unLockCriticalSection(docId);}); -} -function _insertChangesCallback (startIndex, objChanges, docId, index, user, callback) { - var sqlCommand = "INSERT INTO " + tableChanges + " VALUES"; - var i = startIndex, l = objChanges.length, sqlNextRow = "", lengthUtf8Current = 0, lengthUtf8Row = 0; - if (i === l) - return; - - for (; i < l; ++i, ++index) { - sqlNextRow = "(" + baseConnector.sqlEscape(docId) + "," + baseConnector.sqlEscape(index) + "," - + baseConnector.sqlEscape(user.id) + "," + baseConnector.sqlEscape(user.idOriginal) + "," - + baseConnector.sqlEscape(user.username) + "," + baseConnector.sqlEscape(objChanges[i].change) + "," - + baseConnector.sqlEscape(_getDateTime(objChanges[i].time)) + ")"; - lengthUtf8Row = _lengthInUtf8Bytes(sqlNextRow) + 1; // 1 - Ñто на Ñимвол ',' или ';' в конце команды - if (i === startIndex) { - lengthUtf8Current = _lengthInUtf8Bytes(sqlCommand); - sqlCommand += sqlNextRow; - } else { - if (lengthUtf8Row + lengthUtf8Current >= maxPacketSize) { - sqlCommand += ';'; - (function (tmpStart, tmpIndex) { - baseConnector.sqlQuery(sqlCommand, function () { - // lock не Ñнимаем, а продолжаем добавлÑть - _insertChangesCallback(tmpStart, objChanges, docId, tmpIndex, user, callback); - }); - })(i, index); - return; - } else { - sqlCommand += ','; - sqlCommand += sqlNextRow; - } - } - - lengthUtf8Current += lengthUtf8Row; - } - - sqlCommand += ';'; - baseConnector.sqlQuery(sqlCommand, callback); -} -exports.deleteChangesCallback = function (docId, deleteIndex, callback) { - var sqlCommand = "DELETE FROM " + tableChanges + " WHERE id='" + docId + "'"; - if (null !== deleteIndex) - sqlCommand += " AND change_id >= " + deleteIndex; - sqlCommand += ";"; - baseConnector.sqlQuery(sqlCommand, callback); -}; -exports.deleteChangesPromise = function (docId, deleteIndex) { - return new Promise(function(resolve, reject) { - exports.deleteChangesCallback(docId, deleteIndex, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; -exports.deleteChanges = function (docId, deleteIndex) { - lockCriticalSection(docId, function () {_deleteChanges(docId, deleteIndex);}); -}; -function _deleteChanges (docId, deleteIndex) { - exports.deleteChangesCallback(docId, deleteIndex, function () {unLockCriticalSection(docId);}); -} -exports.getChangesIndex = function(docId, callback) { - var table = getTableById(c_oTableId.changes); - var sqlCommand = 'SELECT MAX(change_id) as change_id FROM ' + table + ' WHERE id=' + baseConnector.sqlEscape(docId) + ';'; - baseConnector.sqlQuery(sqlCommand, callback); -}; -exports.getChangesIndexPromise = function(docId) { - return new Promise(function(resolve, reject) { - exports.getChangesIndex(docId, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; -exports.getChangesPromise = function (docId, optStartIndex, optEndIndex) { - return new Promise(function(resolve, reject) { - var getCondition = 'id='+baseConnector.sqlEscape(docId); - if (null != optStartIndex && null != optEndIndex) { - getCondition += ' AND change_id>=' + optStartIndex + ' AND change_id<' + optEndIndex; - } - getCondition += ' ORDER BY change_id ASC'; - getDataFromTable(c_oTableId.changes, "*", getCondition, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; -exports.getChanges = function (docId, opt_time, opt_index, callback) { - lockCriticalSection(docId, function () {_getChanges(docId, opt_time, opt_index, callback);}); -}; -function _getChanges (docId, opt_time, opt_index, callback) { - var getCondition = "id='" + docId + "'"; - if (null != opt_time && null != opt_index) { - getCondition += " AND change_date<=" + baseConnector.sqlEscape(_getDateTime(opt_time)); - getCondition += " AND change_id<" + baseConnector.sqlEscape(opt_index); - } - getCondition += " ORDER BY change_id ASC"; - getDataFromTable(c_oTableId.changes, "*", getCondition, - function (error, result) {unLockCriticalSection(docId); if (callback) callback(error, result);}); -} - -exports.checkStatusFile = function (docId, callbackFunction) { - var sqlCommand = "SELECT status FROM " + tableResult + " WHERE id='" + docId + "';"; - baseConnector.sqlQuery(sqlCommand, callbackFunction); -}; -exports.checkStatusFilePromise = function (docId) { - return new Promise(function(resolve, reject) { - exports.checkStatusFile(docId, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; -exports.updateStatusFile = function (docId) { - // Ð¡Ñ‚Ð°Ñ‚ÑƒÑ OK = 1 - var sqlCommand = "UPDATE " + tableResult + " SET status=1 WHERE id='" + docId + "';"; - baseConnector.sqlQuery(sqlCommand); -}; - -exports.isLockCriticalSection = function (id) { - return !!(g_oCriticalSection[id]); -}; - -// КритичеÑÐºÐ°Ñ ÑÐµÐºÑ†Ð¸Ñ -function lockCriticalSection (id, callback) { - if (g_oCriticalSection[id]) { - // Ждем - g_oCriticalSection[id].push(callback); - return; - } - // Ставим lock - g_oCriticalSection[id] = []; - g_oCriticalSection[id].push(callback); - callback(); -} -function unLockCriticalSection (id) { - var arrCallbacks = g_oCriticalSection[id]; - arrCallbacks.shift(); - if (0 < arrCallbacks.length) - arrCallbacks[0](); - else - delete g_oCriticalSection[id]; -} -exports.healthCheck = function () { - return new Promise(function(resolve, reject) { - //SELECT 1; usefull for H2, MySQL, Microsoft SQL Server, PostgreSQL, SQLite - //http://stackoverflow.com/questions/3668506/efficient-sql-test-query-or-validation-query-that-will-work-across-all-or-most - baseConnector.sqlQuery('SELECT 1;', function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; - -exports.getEmptyCallbacks = function() { - return new Promise(function(resolve, reject) { - const sqlCommand = "SELECT DISTINCT t1.id FROM doc_changes t1 LEFT JOIN task_result t2 ON t2.id = t1.id WHERE t2.callback = '';"; - baseConnector.sqlQuery(sqlCommand, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; diff --git a/DocService/sources/canvasservice.js b/DocService/sources/canvasservice.js index 672cb4d0e8..845dcab8ea 100644 --- a/DocService/sources/canvasservice.js +++ b/DocService/sources/canvasservice.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -31,13 +31,18 @@ */ 'use strict'; - +const crypto = require('crypto'); var pathModule = require('path'); var urlModule = require('url'); var co = require('co'); -var sqlBase = require('./baseConnector'); +const ms = require('ms'); +const retry = require('retry'); +const MultiRange = require('multi-integer-range').MultiRange; +var sqlBase = require('./databaseConnectors/baseConnector'); +const utilsDocService = require('./utilsDocService'); var docsCoServer = require('./DocsCoServer'); var taskResult = require('./taskresult'); +var wopiClient = require('./wopiClient'); var logger = require('./../../Common/sources/logger'); var utils = require('./../../Common/sources/utils'); var constants = require('./../../Common/sources/constants'); @@ -45,18 +50,28 @@ var commonDefines = require('./../../Common/sources/commondefines'); var storage = require('./../../Common/sources/storage-base'); var formatChecker = require('./../../Common/sources/formatchecker'); var statsDClient = require('./../../Common/sources/statsdclient'); +var operationContext = require('./../../Common/sources/operationContext'); +var tenantManager = require('./../../Common/sources/tenantManager'); var config = require('config'); -var config_server = config.get('services.CoAuthoring.server'); -var config_utils = config.get('services.CoAuthoring.utils'); -var pubsubRedis = require('./pubsubRedis'); - +const path = require("path"); -var cfgTypesUpload = config_utils.get('limits_image_types_upload'); -var cfgTypesCopy = config_utils.get('limits_image_types_copy'); -var cfgImageSize = config_server.get('limits_image_size'); -var cfgImageDownloadTimeout = config_server.get('limits_image_download_timeout'); -var cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix'); -var cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); +const cfgTypesUpload = config.get('services.CoAuthoring.utils.limits_image_types_upload'); +const cfgImageSize = config.get('services.CoAuthoring.server.limits_image_size'); +const cfgImageDownloadTimeout = config.get('services.CoAuthoring.server.limits_image_download_timeout'); +const cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix'); +const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); +const cfgTokenSessionAlgorithm = config.get('services.CoAuthoring.token.session.algorithm'); +const cfgTokenSessionExpires = config.get('services.CoAuthoring.token.session.expires'); +const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles'); +const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname'); +const cfgOpenProtectedFile = config.get('services.CoAuthoring.server.openProtectedFile'); +const cfgExpUpdateVersionStatus = config.get('services.CoAuthoring.expire.updateVersionStatus'); +const cfgCallbackBackoffOptions = config.get('services.CoAuthoring.callbackBackoffOptions'); +const cfgAssemblyFormatAsOrigin = config.get('services.CoAuthoring.server.assemblyFormatAsOrigin'); +const cfgDownloadMaxBytes = config.get('FileConverter.converter.maxDownloadBytes'); +const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout'); +const cfgDownloadFileAllowExt = config.get('services.CoAuthoring.server.downloadFileAllowExt'); +const cfgNewFileTemplate = config.get('services.CoAuthoring.server.newFileTemplate'); var SAVE_TYPE_PART_START = 0; var SAVE_TYPE_PART = 1; @@ -64,9 +79,9 @@ var SAVE_TYPE_COMPLETE = 2; var SAVE_TYPE_COMPLETE_ALL = 3; var clientStatsD = statsDClient.getClient(); -var redisClient = pubsubRedis.getClientRedis(); -var redisKeySaved = cfgRedisPrefix + constants.REDIS_KEY_SAVED; var redisKeyShutdown = cfgRedisPrefix + constants.REDIS_KEY_SHUTDOWN; +let hasPasswordCol = false;//stub on upgradev630.sql update failure +exports.hasAdditionalCol = false;//stub on upgradev710.sql update failure function OutputDataWrap(type, data) { this['type'] = type; @@ -95,12 +110,16 @@ function OutputData(type) { this['type'] = type; this['status'] = undefined; this['data'] = undefined; + this['filetype'] = undefined; + this['openedAt'] = undefined; } OutputData.prototype = { fromObject: function(data) { this['type'] = data['type']; this['status'] = data['status']; this['data'] = data['data']; + this['filetype'] = data['filetype']; + this['openedAt'] = data['openedAt']; }, getType: function() { return this['type']; @@ -119,111 +138,249 @@ OutputData.prototype = { }, setData: function(data) { this['data'] = data; + }, + getExtName: function() { + return this['filetype']; + }, + setExtName: function(data) { + this['filetype'] = data.substring(1); + }, + getOpenedAt: function() { + return this['openedAt']; + }, + setOpenedAt: function(data) { + this['openedAt'] = data; } }; -function* getOutputData(cmd, outputData, key, status, statusInfo, optConn, optAdditionalOutput) { - var docId = cmd.getDocId(); +function getOpenedAt(row) { + if (row) { + return sqlBase.DocumentAdditional.prototype.getOpenedAt(row.additional); + } + return; +} +function getOpenedAtJSONParams(row) { + let documentLayout = row && sqlBase.DocumentAdditional.prototype.getDocumentLayout(row.additional); + if (documentLayout) { + return {'documentLayout': documentLayout}; + } + return undefined; +} + +async function getOutputData(ctx, cmd, outputData, key, optConn, optAdditionalOutput, opt_bIsRestore) { + const tenExpUpdateVersionStatus = ms(ctx.getCfg('services.CoAuthoring.expire.updateVersionStatus', cfgExpUpdateVersionStatus)); + + let status, statusInfo, password, creationDate, openedAt, originFormat, row; + let selectRes = await taskResult.select(ctx, key); + if (selectRes.length > 0) { + row = selectRes[0]; + status = row.status; + statusInfo = row.status_info; + password = sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password); + creationDate = row.created_at && row.created_at.getTime(); + openedAt = getOpenedAt(row); + originFormat = row.change_id; + if (optAdditionalOutput) { + optAdditionalOutput.row = row; + } + } switch (status) { - case taskResult.FileStatus.SaveVersion: - case taskResult.FileStatus.UpdateVersion: - case taskResult.FileStatus.Ok: - if(taskResult.FileStatus.Ok == status) { + case commonDefines.FileStatus.SaveVersion: + case commonDefines.FileStatus.UpdateVersion: + case commonDefines.FileStatus.Ok: + if(commonDefines.FileStatus.Ok === status) { + outputData.setStatus('ok'); + } else if (optConn && optConn.isCloseCoAuthoring) { + outputData.setStatus(constants.FILE_STATUS_UPDATE_VERSION); + } else if (optConn && optConn.user.view) { outputData.setStatus('ok'); - } else if(taskResult.FileStatus.SaveVersion == status) { - if ((optConn && optConn.user.view) || optConn.isCloseCoAuthoring) { - outputData.setStatus('updateversion'); + } else if (commonDefines.FileStatus.SaveVersion === status || + (!opt_bIsRestore && commonDefines.FileStatus.UpdateVersion === status && + Date.now() - statusInfo * 60000 > tenExpUpdateVersionStatus)) { + if (commonDefines.FileStatus.UpdateVersion === status) { + ctx.logger.warn("UpdateVersion expired"); + } + var updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = key; + updateMask.status = status; + updateMask.statusInfo = statusInfo; + var updateTask = new taskResult.TaskResultData(); + updateTask.status = commonDefines.FileStatus.Ok; + updateTask.statusInfo = constants.NO_ERROR; + var updateIfRes = await taskResult.updateIf(ctx, updateTask, updateMask); + if (updateIfRes.affectedRows > 0) { + outputData.setStatus('ok'); } else { - var updateMask = new taskResult.TaskResultData(); - updateMask.key = docId; - updateMask.status = status; - updateMask.statusInfo = statusInfo; - var updateTask = new taskResult.TaskResultData(); - updateTask.status = taskResult.FileStatus.Ok; - updateTask.statusInfo = constants.NO_ERROR; - var updateIfRes = yield taskResult.updateIf(updateTask, updateMask); - if (updateIfRes.affectedRows > 0) { - outputData.setStatus('ok'); - } else { - outputData.setStatus('updateversion'); - } + outputData.setStatus(constants.FILE_STATUS_UPDATE_VERSION); } } else { - outputData.setStatus('updateversion'); + outputData.setStatus(constants.FILE_STATUS_UPDATE_VERSION); } var command = cmd.getCommand(); if ('open' != command && 'reopen' != command && !cmd.getOutputUrls()) { var strPath = key + '/' + cmd.getOutputPath(); if (optConn) { - var contentDisposition = cmd.getInline() ? constants.CONTENT_DISPOSITION_INLINE : constants.CONTENT_DISPOSITION_ATTACHMENT; - outputData.setData(yield storage.getSignedUrl(optConn.baseUrl, strPath, null, cmd.getTitle(), contentDisposition)); + let url; + if(cmd.getInline()) { + url = await getPrintFileUrl(ctx, key, optConn.baseUrl, cmd.getTitle()); + } else { + url = await storage.getSignedUrl(ctx, optConn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary, + cmd.getTitle()); + } + outputData.setData(url); + outputData.setExtName(pathModule.extname(strPath)); } else if (optAdditionalOutput) { - optAdditionalOutput.needUrlKey = strPath; + optAdditionalOutput.needUrlKey = cmd.getInline() ? key : strPath; optAdditionalOutput.needUrlMethod = 2; + optAdditionalOutput.needUrlType = commonDefines.c_oAscUrlTypes.Temporary; } } else { - if (optConn) { - outputData.setData(yield storage.getSignedUrls(optConn.baseUrl, key)); + let encryptedUserPassword = cmd.getPassword(); + let userPassword; + let decryptedPassword; + let isCorrectPassword; + if (password && encryptedUserPassword) { + decryptedPassword = await utils.decryptPassword(ctx, password); + userPassword = await utils.decryptPassword(ctx, encryptedUserPassword); + isCorrectPassword = decryptedPassword === userPassword; + } + let isNeedPassword = password && !isCorrectPassword; + if (isNeedPassword && formatChecker.isBrowserEditorFormat(originFormat)) { + //check pdf form + //todo check without storage + let formEditor = await storage.listObjects(ctx, key + '/Editor.bin'); + isNeedPassword = 0 !== formEditor.length; + } + if (isNeedPassword) { + ctx.logger.debug("getOutputData password mismatch"); + if (encryptedUserPassword) { + outputData.setStatus('needpassword'); + outputData.setData(constants.CONVERT_PASSWORD); + } else { + outputData.setStatus('needpassword'); + outputData.setData(constants.CONVERT_DRM); + } + } else if (optConn) { + outputData.setOpenedAt(openedAt); + outputData.setData(await storage.getSignedUrls(ctx, optConn.baseUrl, key, commonDefines.c_oAscUrlTypes.Session, creationDate)); } else if (optAdditionalOutput) { optAdditionalOutput.needUrlKey = key; optAdditionalOutput.needUrlMethod = 0; + optAdditionalOutput.needUrlType = commonDefines.c_oAscUrlTypes.Session; + optAdditionalOutput.needUrlIsCorrectPassword = isCorrectPassword; + optAdditionalOutput.creationDate = creationDate; + optAdditionalOutput.openedAt = openedAt; } } break; - case taskResult.FileStatus.NeedParams: + case commonDefines.FileStatus.NeedParams: outputData.setStatus('needparams'); - var settingsPath = key + '/' + 'settings.json'; + var settingsPath = key + '/' + 'origin.' + cmd.getFormat(); if (optConn) { - outputData.setData(yield storage.getSignedUrl(optConn.baseUrl, settingsPath)); + let url = await storage.getSignedUrl(ctx, optConn.baseUrl, settingsPath, commonDefines.c_oAscUrlTypes.Temporary); + outputData.setData(url); } else if (optAdditionalOutput) { optAdditionalOutput.needUrlKey = settingsPath; optAdditionalOutput.needUrlMethod = 1; + optAdditionalOutput.needUrlType = commonDefines.c_oAscUrlTypes.Temporary; } break; - case taskResult.FileStatus.NeedPassword: + case commonDefines.FileStatus.NeedPassword: outputData.setStatus('needpassword'); outputData.setData(statusInfo); break; - case taskResult.FileStatus.Err: - case taskResult.FileStatus.ErrToReload: + case commonDefines.FileStatus.Err: outputData.setStatus('err'); outputData.setData(statusInfo); - if (taskResult.FileStatus.ErrToReload == status) { - yield cleanupCache(key); - } + break; + case commonDefines.FileStatus.ErrToReload: + outputData.setStatus('err'); + outputData.setData(statusInfo); + await cleanupErrToReload(ctx, key); + break; + case commonDefines.FileStatus.None: + //this status has no handler + break; + case commonDefines.FileStatus.WaitQueue: + //task in the queue. response will be after convertion + break; + default: + outputData.setStatus('err'); + outputData.setData(constants.UNKNOWN); break; } + return status; +} +function* addRandomKeyTaskCmd(ctx, cmd) { + let docId = cmd.getDocId(); + let task = yield* taskResult.addRandomKeyTask(ctx, docId); + //set saveKey as postfix to fix vulnerability with path traversal to docId or other files + cmd.setSaveKey(task.key.substring(docId.length)); +} +function addPasswordToCmd(ctx, cmd, docPasswordStr, originFormat) { + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(ctx, docPasswordStr); + if (docPassword.current) { + if (formatChecker.isBrowserEditorFormat(originFormat)) { + //todo not allowed different password + cmd.setPassword(docPassword.current); + } + cmd.setSavePassword(docPassword.current); + } + if (docPassword.change) { + cmd.setExternalChangeInfo(docPassword.change); + } +} +function addOriginFormat(ctx, cmd, row) { + cmd.setOriginFormat(row && row.change_id); } -function* addRandomKeyTaskCmd(cmd) { - var task = yield* taskResult.addRandomKeyTask(cmd.getDocId()); - cmd.setSaveKey(task.key); + +function changeFormatByOrigin(ctx, row, format) { + const tenAssemblyFormatAsOrigin = ctx.getCfg('services.CoAuthoring.server.assemblyFormatAsOrigin', cfgAssemblyFormatAsOrigin); + + let originFormat = row && row.change_id; + if (originFormat && constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== originFormat) { + if (tenAssemblyFormatAsOrigin) { + format = originFormat; + } else { + //for wopi always save origin + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + let wopiParams = wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback); + if (wopiParams) { + format = originFormat; + } + } + } + return format; } -function* saveParts(cmd) { +function* saveParts(ctx, cmd, filename) { var result = false; var saveType = cmd.getSaveType(); - var filename; - if (SAVE_TYPE_COMPLETE_ALL === saveType) { - filename = 'Editor.bin'; - } else { - filename = 'Editor' + (cmd.getSaveIndex() || '') + '.bin'; + if (SAVE_TYPE_COMPLETE_ALL !== saveType) { + let ext = pathModule.extname(filename); + let saveIndex = parseInt(cmd.getSaveIndex()) || 1;//prevent path traversal + filename = pathModule.basename(filename, ext) + saveIndex + ext; } - if (SAVE_TYPE_PART_START === saveType || SAVE_TYPE_COMPLETE_ALL === saveType) { - yield* addRandomKeyTaskCmd(cmd); + if ((SAVE_TYPE_PART_START === saveType || SAVE_TYPE_COMPLETE_ALL === saveType) && !cmd.getSaveKey()) { + yield* addRandomKeyTaskCmd(ctx, cmd); } if (cmd.getUrl()) { result = true; - } else { + } else if (cmd.getData() && cmd.getData().length > 0 && cmd.getSaveKey()) { var buffer = cmd.getData(); - yield storage.putObject(cmd.getSaveKey() + '/' + filename, buffer, buffer.length); + yield storage.putObject(ctx, cmd.getDocId() + cmd.getSaveKey() + '/' + filename, buffer, buffer.length); //delete data to prevent serialize into json cmd.data = null; result = (SAVE_TYPE_COMPLETE_ALL === saveType || SAVE_TYPE_COMPLETE === saveType); + } else { + result = true; } return result; } -function getSaveTask(cmd) { +function getSaveTask(ctx, cmd) { cmd.setData(null); var queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); queueData.setCmd(cmd); queueData.setToFile(constants.OUTPUT_NAME + '.' + formatChecker.getStringFromFormat(cmd.getOutputFormat())); //todo paid @@ -235,129 +392,251 @@ function getSaveTask(cmd) { //} return queueData; } -function getUpdateResponse(cmd) { +async function getUpdateResponse(ctx, cmd) { + const tenOpenProtectedFile = ctx.getCfg('services.CoAuthoring.server.openProtectedFile', cfgOpenProtectedFile); + var updateTask = new taskResult.TaskResultData(); - updateTask.key = cmd.getSaveKey() ? cmd.getSaveKey() : cmd.getDocId(); + updateTask.tenant = ctx.tenant; + updateTask.key = cmd.getDocId(); + if (cmd.getSaveKey()) { + updateTask.key += cmd.getSaveKey(); + } var statusInfo = cmd.getStatusInfo(); - if (constants.NO_ERROR == statusInfo) { - updateTask.status = taskResult.FileStatus.Ok; - } else if (constants.CONVERT_DOWNLOAD == statusInfo) { - updateTask.status = taskResult.FileStatus.ErrToReload; - } else if (constants.CONVERT_NEED_PARAMS == statusInfo) { - updateTask.status = taskResult.FileStatus.NeedParams; - } else if (constants.CONVERT_DRM == statusInfo || constants.CONVERT_PASSWORD == statusInfo) { - updateTask.status = taskResult.FileStatus.NeedPassword; + if (constants.NO_ERROR === statusInfo) { + updateTask.status = commonDefines.FileStatus.Ok; + let password = cmd.getPassword(); + if (password) { + if (false === hasPasswordCol) { + let selectRes = await taskResult.select(ctx, updateTask.key); + hasPasswordCol = selectRes.length > 0 && undefined !== selectRes[0].password; + } + if(hasPasswordCol) { + updateTask.password = password; + } + } + } else if (constants.CONVERT_TEMPORARY === statusInfo) { + updateTask.status = commonDefines.FileStatus.ErrToReload; + } else if (constants.CONVERT_DOWNLOAD === statusInfo) { + updateTask.status = commonDefines.FileStatus.ErrToReload; + } else if (constants.CONVERT_LIMITS === statusInfo) { + updateTask.status = commonDefines.FileStatus.ErrToReload; + } else if (constants.CONVERT_NEED_PARAMS === statusInfo) { + updateTask.status = commonDefines.FileStatus.NeedParams; + } else if (constants.CONVERT_DRM === statusInfo || constants.CONVERT_PASSWORD === statusInfo) { + if (tenOpenProtectedFile) { + updateTask.status = commonDefines.FileStatus.NeedPassword; + } else { + updateTask.status = commonDefines.FileStatus.Err; + } + } else if (constants.CONVERT_DRM_UNSUPPORTED === statusInfo) { + updateTask.status = commonDefines.FileStatus.Err; + } else if (constants.CONVERT_DEAD_LETTER === statusInfo) { + updateTask.status = commonDefines.FileStatus.ErrToReload; } else { - updateTask.status = taskResult.FileStatus.Err; + updateTask.status = commonDefines.FileStatus.Err; } updateTask.statusInfo = statusInfo; return updateTask; } -var cleanupCache = co.wrap(function* (docId) { +var cleanupCache = co.wrap(function* (ctx, docId) { + //todo redis ? + var res = false; + var removeRes = yield taskResult.remove(ctx, docId); + if (removeRes.affectedRows > 0) { + yield storage.deletePath(ctx, docId); + res = true; + } + ctx.logger.debug("cleanupCache docId=%s db.affectedRows=%d", docId, removeRes.affectedRows); + return res; +}); +var cleanupCacheIf = co.wrap(function* (ctx, mask) { //todo redis ? var res = false; - var removeRes = yield taskResult.remove(docId); + var removeRes = yield taskResult.removeIf(ctx, mask); if (removeRes.affectedRows > 0) { - yield storage.deletePath(docId); + sqlBase.deleteChanges(ctx, mask.key, null); + yield storage.deletePath(ctx, mask.key); res = true; } + ctx.logger.debug("cleanupCacheIf db.affectedRows=%d", removeRes.affectedRows); return res; }); +async function cleanupErrToReload(ctx, key) { + let updateTask = new taskResult.TaskResultData(); + updateTask.tenant = ctx.tenant; + updateTask.key = key; + updateTask.status = commonDefines.FileStatus.None; + updateTask.statusInfo = constants.NO_ERROR; + await taskResult.update(ctx, updateTask); +} -function commandOpenStartPromise(docId, cmd, opt_updateUserIndex, opt_documentCallbackUrl, opt_baseUrl) { +function commandOpenStartPromise(ctx, docId, baseUrl, opt_documentCallbackUrl, opt_format) { var task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; task.key = docId; - task.status = taskResult.FileStatus.WaitQueue; + //None instead WaitQueue to prevent: conversion task is lost when entering and leaving the editor quickly(that leads to an endless opening) + task.status = commonDefines.FileStatus.None; task.statusInfo = constants.NO_ERROR; - if (opt_documentCallbackUrl && opt_baseUrl) { + task.baseurl = baseUrl; + if (opt_documentCallbackUrl) { task.callback = opt_documentCallbackUrl; - task.baseurl = opt_baseUrl; } - if (!cmd) { - logger.warn("commandOpenStartPromise empty cmd: docId = %s", docId); + if (opt_format) { + task.changeId = formatChecker.getFormatFromString(opt_format); } - - return taskResult.upsert(task, opt_updateUserIndex); + return taskResult.upsert(ctx, task); } -function* commandOpen(conn, cmd, outputData, opt_upsertRes) { +function* commandOpen(ctx, conn, cmd, outputData, opt_upsertRes, opt_bIsRestore) { + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + var upsertRes; if (opt_upsertRes) { upsertRes = opt_upsertRes; } else { - upsertRes = yield commandOpenStartPromise(cmd.getDocId(), cmd); + upsertRes = yield commandOpenStartPromise(ctx, cmd.getDocId(), utils.getBaseUrlByConnection(ctx, conn), undefined, cmd.getFormat()); } - //if CLIENT_FOUND_ROWS don't specify 1 row is inserted , 2 row is updated, and 0 row is set to its current values - //http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html - var bCreate = upsertRes.affectedRows == 1; + let bCreate = upsertRes.isInsert; + let needAddTask = bCreate; if (!bCreate) { - var selectRes = yield taskResult.select(cmd.getDocId()); - if (selectRes.length > 0) { - var row = selectRes[0]; - yield* getOutputData(cmd, outputData, cmd.getDocId(), row.status, row.status_info, conn); + needAddTask = yield* commandOpenFillOutput(ctx, conn, cmd, outputData, opt_bIsRestore); + } + if (conn.encrypted) { + ctx.logger.debug("commandOpen encrypted %j", outputData); + if (constants.FILE_STATUS_UPDATE_VERSION !== outputData.getStatus()) { + //don't send output data + outputData.setStatus(undefined); + } + } else if (needAddTask) { + let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = cmd.getDocId(); + updateMask.status = commonDefines.FileStatus.None; + + let task = new taskResult.TaskResultData(); + task.status = commonDefines.FileStatus.WaitQueue; + task.statusInfo = constants.NO_ERROR; + + let updateIfRes = yield taskResult.updateIf(ctx, task, updateMask); + if (updateIfRes.affectedRows > 0) { + let forgotten = yield storage.listObjects(ctx, cmd.getDocId(), tenForgottenFiles); + //replace url with forgotten file because it absorbed all lost changes + if (forgotten.length > 0) { + ctx.logger.debug("commandOpen from forgotten"); + cmd.setUrl(undefined); + cmd.setForgotten(cmd.getDocId()); + } + //add task + if (!cmd.getOutputFormat()) { + //todo remove getOpenFormatByEditor after 8.2.1 + cmd.setOutputFormat(docsCoServer.getOpenFormatByEditor(conn.editorType)); + } + cmd.setEmbeddedFonts(false); + var dataQueue = new commonDefines.TaskQueueData(); + dataQueue.setCtx(ctx); + dataQueue.setCmd(cmd); + dataQueue.setToFile('Editor.bin'); + var priority = constants.QUEUE_PRIORITY_HIGH; + var formatIn = formatChecker.getFormatFromString(cmd.getFormat()); + //decrease pdf, djvu, xps convert priority becase long open time + if (constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === formatIn || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_DJVU === formatIn || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_XPS === formatIn) { + priority = constants.QUEUE_PRIORITY_LOW; + } + yield* docsCoServer.addTask(dataQueue, priority); + } else { + yield* commandOpenFillOutput(ctx, conn, cmd, outputData, opt_bIsRestore); + } } - } else { - //add task - cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_CANVAS); - cmd.setEmbeddedFonts(false); - var dataQueue = new commonDefines.TaskQueueData(); - dataQueue.setCmd(cmd); - dataQueue.setToFile('Editor.bin'); - var priority = constants.QUEUE_PRIORITY_HIGH; - var formatIn = formatChecker.getFormatFromString(cmd.getFormat()); - //decrease pdf, djvu, xps convert priority becase long open time - if (constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === formatIn || constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_DJVU === formatIn || - constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_XPS === formatIn) { - priority = constants.QUEUE_PRIORITY_LOW; - } - yield* docsCoServer.addTask(dataQueue, priority); } +function* commandOpenFillOutput(ctx, conn, cmd, outputData, opt_bIsRestore) { + let status = yield getOutputData(ctx, cmd, outputData, cmd.getDocId(), conn, undefined, opt_bIsRestore); + return commonDefines.FileStatus.None === status; } -function* commandReopen(cmd) { - var task = new taskResult.TaskResultData(); - task.key = cmd.getDocId(); - task.status = taskResult.FileStatus.WaitQueue; - task.statusInfo = constants.NO_ERROR; +function* commandReopen(ctx, conn, cmd, outputData) { + const tenOpenProtectedFile = ctx.getCfg('services.CoAuthoring.server.openProtectedFile', cfgOpenProtectedFile); + + let res = true; + let isPassword = undefined !== cmd.getPassword(); + if (isPassword) { + let selectRes = yield taskResult.select(ctx, cmd.getDocId()); + if (selectRes.length > 0) { + let row = selectRes[0]; + if (sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password)) { + ctx.logger.debug('commandReopen has password'); + yield* commandOpenFillOutput(ctx, conn, cmd, outputData, false); + yield docsCoServer.modifyConnectionForPassword(ctx, conn, constants.FILE_STATUS_OK === outputData.getStatus()); + return res; + } + } + } + if (!isPassword || tenOpenProtectedFile) { + let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = cmd.getDocId(); + updateMask.status = isPassword ? commonDefines.FileStatus.NeedPassword : commonDefines.FileStatus.NeedParams; + + var task = new taskResult.TaskResultData(); + task.status = commonDefines.FileStatus.WaitQueue; + task.statusInfo = constants.NO_ERROR; - var upsertRes = yield taskResult.update(task); - if (upsertRes.affectedRows > 0) { - //add task - cmd.setUrl(null);//url may expire - cmd.setSaveKey(cmd.getDocId()); - cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_CANVAS); - cmd.setEmbeddedFonts(false); - var dataQueue = new commonDefines.TaskQueueData(); - dataQueue.setCmd(cmd); - dataQueue.setToFile('Editor.bin'); - dataQueue.setFromSettings(true); - yield* docsCoServer.addTask(dataQueue, constants.QUEUE_PRIORITY_HIGH); + var upsertRes = yield taskResult.updateIf(ctx, task, updateMask); + if (upsertRes.affectedRows > 0) { + //add task + cmd.setUrl(null);//url may expire + if (!cmd.getOutputFormat()) { + //todo remove getOpenFormatByEditor after 8.2.1 + cmd.setOutputFormat(docsCoServer.getOpenFormatByEditor(conn.editorType)); + } + cmd.setEmbeddedFonts(false); + if (isPassword) { + cmd.setUserConnectionId(conn.user.id); + } + var dataQueue = new commonDefines.TaskQueueData(); + dataQueue.setCtx(ctx); + dataQueue.setCmd(cmd); + dataQueue.setToFile('Editor.bin'); + dataQueue.setFromSettings(true); + yield* docsCoServer.addTask(dataQueue, constants.QUEUE_PRIORITY_HIGH); + } else { + outputData.setStatus('needpassword'); + outputData.setData(constants.CONVERT_PASSWORD); + } + } else { + res = false; } + return res; } -function* commandSave(cmd, outputData) { - var completeParts = yield* saveParts(cmd); +function* commandSave(ctx, cmd, outputData) { + let format = cmd.getFormat() || 'bin'; + var completeParts = yield* saveParts(ctx, cmd, "Editor." + format); if (completeParts) { - var queueData = getSaveTask(cmd); + var queueData = getSaveTask(ctx, cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); } outputData.setStatus('ok'); outputData.setData(cmd.getSaveKey()); } -function* commandSendMailMerge(cmd, outputData) { - var completeParts = yield* saveParts(cmd); +function* commandSendMailMerge(ctx, cmd, outputData) { + let mailMergeSend = cmd.getMailMergeSend(); + let isJson = mailMergeSend.getIsJsonKey(); + var completeParts = yield* saveParts(ctx, cmd, isJson ? "Editor.json" : "Editor.bin"); var isErr = false; - if (completeParts) { + if (completeParts && !isJson) { isErr = true; - var getRes = yield* docsCoServer.getCallback(cmd.getDocId()); - if (getRes) { - var mailMergeSend = cmd.getMailMergeSend(); + var getRes = yield docsCoServer.getCallback(ctx, cmd.getDocId(), cmd.getUserIndex()); + if (getRes && !getRes.wopiParams) { mailMergeSend.setUrl(getRes.server.href); mailMergeSend.setBaseUrl(getRes.baseUrl); - //менÑем JsonKey и SaveKey, новый key нужет потому что за одну конвертацию делаетÑÑ Ñ‡Ð°Ñть, а json нужен вÑегда + //we change JsonKey and SaveKey, a new key is needed because a part is done in one conversion, and json is always needed mailMergeSend.setJsonKey(cmd.getSaveKey()); mailMergeSend.setRecordErrorCount(0); - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); isErr = false; + } else if (getRes.wopiParams) { + ctx.logger.warn('commandSendMailMerge unexpected with wopi'); } } if (isErr) { @@ -368,27 +647,39 @@ function* commandSendMailMerge(cmd, outputData) { outputData.setData(cmd.getSaveKey()); } } -function* commandSfctByCmd(cmd, opt_priority, opt_expiration, opt_queue) { - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); +let commandSfctByCmd = co.wrap(function*(ctx, cmd, opt_priority, opt_expiration, opt_queue, opt_initShardKey) { + var selectRes = yield taskResult.select(ctx, cmd.getDocId()); + var row = selectRes.length > 0 ? selectRes[0] : null; + if (!row) { + return false; + } + if (opt_initShardKey) { + ctx.setShardKey(sqlBase.DocumentAdditional.prototype.getShardKey(row.additional)); + ctx.setWopiSrc(sqlBase.DocumentAdditional.prototype.getWopiSrc(row.additional)); + } + yield* addRandomKeyTaskCmd(ctx, cmd); + addPasswordToCmd(ctx, cmd, row.password, row.change_id); + addOriginFormat(ctx, cmd, row); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback)); + cmd.setOutputFormat(changeFormatByOrigin(ctx, row, cmd.getOutputFormat())); + cmd.appendJsonParams(getOpenedAtJSONParams(row)); + var queueData = getSaveTask(ctx, cmd); queueData.setFromChanges(true); let priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW; yield* docsCoServer.addTask(queueData, priority, opt_queue, opt_expiration); -} -function* commandSfct(cmd, outputData) { - yield* commandSfctByCmd(cmd); - outputData.setStatus('ok'); -} + return true; +}); function isDisplayedImage(strName) { var res = 0; if (strName) { - //шаблон display[N]image.ext + //template display[N]image.ext var findStr = constants.DISPLAY_PREFIX; var index = strName.indexOf(findStr); if (-1 != index) { if (index + findStr.length < strName.length) { var displayN = parseInt(strName[index + findStr.length]); - if (1 <= displayN && displayN <= 6) { + if (!isNaN(displayN)) { var imageIndex = index + findStr.length + 1; if (imageIndex == strName.indexOf("image", imageIndex)) res = displayN; @@ -398,108 +689,169 @@ function isDisplayedImage(strName) { } return res; } -function* commandImgurls(conn, cmd, outputData) { - var supportedFormats; - var urls; - var docId = cmd.getDocId(); +function* commandImgurls(ctx, conn, cmd, outputData) { + const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload); + const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize); + const tenImageDownloadTimeout = ctx.getCfg('services.CoAuthoring.server.limits_image_download_timeout', cfgImageDownloadTimeout); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + var errorCode = constants.NO_ERROR; - if (!conn.user.view && !conn.isCloseCoAuthoring) { - var isImgUrl = 'imgurl' == cmd.getCommand(); - if (isImgUrl) { - urls = [cmd.getData()]; - supportedFormats = cfgTypesUpload || 'jpg'; + let urls = cmd.getData(); + let authorizations = []; + let isInJwtToken = false; + let token = cmd.getTokenDownload(); + if (tenTokenEnableBrowser && token) { + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser); + if (checkJwtRes.decoded) { + //todo multiple url case + if (checkJwtRes.decoded.images) { + urls = checkJwtRes.decoded.images.map(function(curValue) { + return curValue.url; + }); + } else { + urls = [checkJwtRes.decoded.url]; + } + for (let i = 0; i < urls.length; ++i) { + if (utils.canIncludeOutboxAuthorization(ctx, urls[i])) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + authorizations[i] = [utils.fillJwtForRequest(ctx, {url: urls[i]}, secret, false)]; + } + } + isInJwtToken = true; } else { - urls = cmd.getData(); - supportedFormats = cfgTypesCopy || 'jpg'; + ctx.logger.warn('Error commandImgurls jwt: %s', checkJwtRes.description); + errorCode = constants.VKEY_ENCRYPT; } + } + var supportedFormats = tenTypesUpload || 'jpg'; + var outputUrls = []; + if (constants.NO_ERROR === errorCode && !conn.user.view && !conn.isCloseCoAuthoring) { //todo Promise.all() - var displayedImageMap = {};//to make one imageIndex for ole object urls - var imageCount = 0; - var outputUrls = []; + let displayedImageMap = {};//to make one prefix for ole object urls for (var i = 0; i < urls.length; ++i) { var urlSource = urls[i]; var urlParsed; var data = undefined; if (urlSource.startsWith('data:')) { - var delimiterIndex = urlSource.indexOf(','); - if (-1 != delimiterIndex && (urlSource.length - (delimiterIndex + 1)) * 0.75 <= cfgImageSize) { - data = new Buffer(urlSource.substring(delimiterIndex + 1), 'base64'); + let delimiterIndex = urlSource.indexOf(','); + if (-1 != delimiterIndex) { + let dataLen = urlSource.length - (delimiterIndex + 1); + if ('hex' === urlSource.substring(delimiterIndex - 3, delimiterIndex).toLowerCase()) { + if (dataLen * 0.5 <= tenImageSize) { + data = Buffer.from(urlSource.substring(delimiterIndex + 1), 'hex'); + } else { + errorCode = constants.UPLOAD_CONTENT_LENGTH; + } + } else { + if (dataLen * 0.75 <= tenImageSize) { + data = Buffer.from(urlSource.substring(delimiterIndex + 1), 'base64'); + } else { + errorCode = constants.UPLOAD_CONTENT_LENGTH; + } + } } } else if (urlSource) { try { + if (authorizations[i]) { + let urlParsed = urlModule.parse(urlSource); + let filterStatus = yield* utils.checkHostFilter(ctx, urlParsed.hostname); + if (0 !== filterStatus) { + throw Error('checkIpFilter'); + } + } //todo stream - data = yield utils.downloadUrlPromise(urlSource, cfgImageDownloadTimeout * 1000, cfgImageSize); + let getRes = yield utils.downloadUrlPromise(ctx, urlSource, tenImageDownloadTimeout, tenImageSize, authorizations[i], isInJwtToken); + data = getRes.body; urlParsed = urlModule.parse(urlSource); } catch (e) { data = undefined; - logger.error('error commandImgurls download: url = %s; docId = %s\r\n%s', urlSource, docId, e.stack); - errorCode = constants.UPLOAD_URL; - break; + ctx.logger.error('error commandImgurls download: url = %s; %s', urlSource, e.stack); + if (e.code === 'EMSGSIZE') { + errorCode = constants.UPLOAD_CONTENT_LENGTH; + } else { + errorCode = constants.UPLOAD_URL; + } } } + + data = yield utilsDocService.fixImageExifRotation(ctx, data); + var outputUrl = {url: 'error', path: 'error'}; if (data) { - var format = formatChecker.getImageFormat(data); - var formatStr; - if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN == format && urlParsed) { - //bin, txt occur in ole object case - var ext = pathModule.extname(urlParsed.pathname); - if ('.bin' == ext || '.txt' == ext) { - formatStr = ext.substring(1); - } - } else { + let format = formatChecker.getImageFormat(ctx, data); + let formatStr; + let isAllow = false; + if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== format) { formatStr = formatChecker.getStringFromFormat(format); + if (formatStr && -1 !== supportedFormats.indexOf(formatStr)) { + isAllow = true; + } } - if (formatStr && -1 !== supportedFormats.indexOf(formatStr)) { - var userid = cmd.getUserId(); - var imageIndex = cmd.getSaveIndex() + imageCount; - imageCount++; - var strLocalPath = 'media/' + utils.crc32(userid).toString(16) + '_'; + if (!isAllow && urlParsed) { + //for ole object, presentation video/audio + let ext = pathModule.extname(urlParsed.pathname).substring(1); + let urlBasename = pathModule.basename(urlParsed.pathname); + let displayedImageName = urlBasename.substring(0, urlBasename.length - ext.length - 1); + if (displayedImageMap.hasOwnProperty(displayedImageName)) { + formatStr = ext; + isAllow = true; + } + } + if (isAllow) { + if (format === constants.AVS_OFFICESTUDIO_FILE_IMAGE_TIFF) { + data = yield utilsDocService.convertImageToPng(ctx, data); + format = constants.AVS_OFFICESTUDIO_FILE_IMAGE_PNG; + formatStr = formatChecker.getStringFromFormat(format); + } + let strLocalPath = 'media/' + crypto.randomBytes(16).toString("hex") + '_'; if (urlParsed) { var urlBasename = pathModule.basename(urlParsed.pathname); var displayN = isDisplayedImage(urlBasename); if (displayN > 0) { var displayedImageName = urlBasename.substring(0, urlBasename.length - formatStr.length - 1); - var tempIndex = displayedImageMap[displayedImageName]; - if (null != tempIndex) { - imageIndex = tempIndex; - imageCount--; + if (displayedImageMap[displayedImageName]) { + strLocalPath = displayedImageMap[displayedImageName]; } else { - displayedImageMap[displayedImageName] = imageIndex; + displayedImageMap[displayedImageName] = strLocalPath; } strLocalPath += constants.DISPLAY_PREFIX + displayN; } } - strLocalPath += 'image' + imageIndex + '.' + formatStr; + strLocalPath += 'image1' + '.' + formatStr; var strPath = cmd.getDocId() + '/' + strLocalPath; - yield storage.putObject(strPath, data, data.length); - var imgUrl = yield storage.getSignedUrl(conn.baseUrl, strPath); + yield storage.putObject(ctx, strPath, data, data.length); + var imgUrl = yield storage.getSignedUrl(ctx, conn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Session); outputUrl = {url: imgUrl, path: strLocalPath}; } } - if (isImgUrl && ('error' === outputUrl.url || 'error' === outputUrl.path)) { + if (constants.NO_ERROR === errorCode && ('error' === outputUrl.url || 'error' === outputUrl.path)) { errorCode = constants.UPLOAD_EXTENSION; - break; } outputUrls.push(outputUrl); } - } else { - logger.error('error commandImgurls: docId = %s access deny', docId); - errorCode = errorCode.UPLOAD; + } else if(constants.NO_ERROR === errorCode) { + ctx.logger.warn('error commandImgurls: access deny'); + errorCode = constants.UPLOAD; } - if (constants.NO_ERROR !== errorCode) { + if (constants.NO_ERROR !== errorCode && 0 == outputUrls.length) { outputData.setStatus('err'); outputData.setData(errorCode); } else { outputData.setStatus('ok'); - outputData.setData(outputUrls); + outputData.setData({error: errorCode, urls: outputUrls}); } } -function* commandPathUrl(conn, cmd, outputData) { - var contentDisposition = cmd.getInline() ? constants.CONTENT_DISPOSITION_INLINE : - constants.CONTENT_DISPOSITION_ATTACHMENT; - var strPath = cmd.getDocId() + '/' + cmd.getData(); - var url = yield storage.getSignedUrl(conn.baseUrl, strPath, null, cmd.getTitle(), contentDisposition); +function* commandPathUrls(ctx, conn, data, outputData) { + let listImages = data.map(function callback(currentValue) { + return conn.docId + '/' + currentValue; + }); + let urls = yield storage.getSignedUrlsArrayByArray(ctx, conn.baseUrl, listImages, commonDefines.c_oAscUrlTypes.Session); + outputData.setStatus('ok'); + outputData.setData(urls); +} +function* commandPathUrl(ctx, conn, cmd, outputData) { + var strPath = conn.docId + '/' + cmd.getData(); + var url = yield storage.getSignedUrl(ctx, conn.baseUrl, strPath, commonDefines.c_oAscUrlTypes.Temporary, cmd.getTitle()); var errorCode = constants.NO_ERROR; if (constants.NO_ERROR !== errorCode) { outputData.setStatus('err'); @@ -507,162 +859,449 @@ function* commandPathUrl(conn, cmd, outputData) { } else { outputData.setStatus('ok'); outputData.setData(url); + outputData.setExtName(pathModule.extname(strPath)); } } -function* commandSaveFromOrigin(cmd, outputData) { - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); - queueData.setFromOrigin(true); - yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); +function* commandSaveFromOrigin(ctx, cmd, outputData, password) { + var completeParts = yield* saveParts(ctx, cmd, "changes0.json"); + if (completeParts) { + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(ctx, password); + if (docPassword.initial) { + cmd.setPassword(docPassword.initial); + } + //todo setLCID in browser + var queueData = getSaveTask(ctx, cmd); + queueData.setFromOrigin(true); + queueData.setFromChanges(true); + yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); + } outputData.setStatus('ok'); outputData.setData(cmd.getSaveKey()); } -function* commandSfcCallback(cmd, isSfcm) { +function* commandSetPassword(ctx, conn, cmd, outputData) { + const tenOpenProtectedFile = ctx.getCfg('services.CoAuthoring.server.openProtectedFile', cfgOpenProtectedFile); + + let hasDocumentPassword = false; + let isDocumentPasswordModified = true; + let selectRes = yield taskResult.select(ctx, cmd.getDocId()); + if (selectRes.length > 0) { + let row = selectRes[0]; + hasPasswordCol = undefined !== row.password; + if (commonDefines.FileStatus.Ok === row.status) { + let documentPasswordCurEnc = sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password); + if (documentPasswordCurEnc) { + hasDocumentPassword = true; + if (cmd.getPassword()) { + const passwordCurPlain = yield utils.decryptPassword(ctx, documentPasswordCurEnc); + const passwordPlain = yield utils.decryptPassword(ctx, cmd.getPassword()); + isDocumentPasswordModified = passwordCurPlain !== passwordPlain; + } + } + } + } + //https://github.com/ONLYOFFICE/web-apps/blob/4a7879b4f88f315fe94d9f7d97c0ed8aa9f82221/apps/documenteditor/main/app/controller/Main.js#L1652 + //this.appOptions.isPasswordSupport = this.appOptions.isEdit && this.api.asc_isProtectionSupport() && (this.permissions.protect!==false); + let isPasswordSupport = tenOpenProtectedFile && !conn.user?.view && false !== conn.permissions?.protect; + ctx.logger.debug('commandSetPassword isEnterCorrectPassword=%s, hasDocumentPassword=%s, hasPasswordCol=%s, isPasswordSupport=%s', conn.isEnterCorrectPassword, hasDocumentPassword, hasPasswordCol, isPasswordSupport); + if (isPasswordSupport && hasPasswordCol && hasDocumentPassword && !isDocumentPasswordModified) { + outputData.setStatus('ok'); + } else if (isPasswordSupport && (conn.isEnterCorrectPassword || !hasDocumentPassword) && hasPasswordCol) { + let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = cmd.getDocId(); + updateMask.status = commonDefines.FileStatus.Ok; + + let newChangesLastDate = new Date(); + newChangesLastDate.setMilliseconds(0);//remove milliseconds avoid issues with MySQL datetime rounding + + var task = new taskResult.TaskResultData(); + task.password = cmd.getPassword() || ""; + let changeInfo = null; + if (conn.user) { + changeInfo = task.innerPasswordChange = docsCoServer.getExternalChangeInfo(conn.user, newChangesLastDate.getTime(), conn.lang); + } + + var upsertRes = yield taskResult.updateIf(ctx, task, updateMask); + if (upsertRes.affectedRows > 0) { + outputData.setStatus('ok'); + if (!conn.isEnterCorrectPassword) { + yield docsCoServer.modifyConnectionForPassword(ctx, conn, true); + } + let forceSave = yield docsCoServer.editorData.getForceSave(ctx, cmd.getDocId()); + let index = forceSave?.index || 0; + yield docsCoServer.resetForceSaveAfterChanges(ctx, cmd.getDocId(), newChangesLastDate.getTime(), index, utils.getBaseUrlByConnection(ctx, conn), changeInfo); + } else { + ctx.logger.debug('commandSetPassword sql update error'); + outputData.setStatus('err'); + outputData.setData(constants.PASSWORD); + } + } else { + outputData.setStatus('err'); + outputData.setData(constants.PASSWORD); + } +} +function* commandChangeDocInfo(ctx, conn, cmd, outputData) { + let res = yield docsCoServer.changeConnectionInfo(ctx, conn, cmd); + if(res) { + outputData.setStatus('ok'); + } else { + outputData.setStatus('err'); + outputData.setData(constants.CHANGE_DOC_INFO); + } +} +function checkAndFixAuthorizationLength(authorization, data){ + //todo it is stub (remove in future versions) + //8kb(https://stackoverflow.com/questions/686217/maximum-on-http-header-values) - 1kb(for other headers) + let res = authorization.length < 7168; + if (!res) { + data.setChangeUrl(undefined); + data.setChangeHistory({}); + } + return res; +} +const commandSfcCallback = co.wrap(function*(ctx, cmd, isSfcm, isEncrypted) { + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + const tenForgottenFilesName = ctx.getCfg('services.CoAuthoring.server.forgottenfilesname', cfgForgottenFilesName); + const tenCallbackBackoffOptions = ctx.getCfg('services.CoAuthoring.callbackBackoffOptions', cfgCallbackBackoffOptions); + var docId = cmd.getDocId(); - logger.debug('Start commandSfcCallback: docId = %s', docId); - var saveKey = cmd.getSaveKey(); + ctx.logger.debug('Start commandSfcCallback'); var statusInfo = cmd.getStatusInfo(); - var isError = constants.NO_ERROR != statusInfo; - var isErrorCorrupted = constants.CONVERT_CORRUPTED == statusInfo; - var savePathDoc = saveKey + '/' + cmd.getOutputPath(); - var savePathChanges = saveKey + '/changes.zip'; - var savePathHistory = saveKey + '/changesHistory.json'; - var getRes = yield* docsCoServer.getCallback(docId); - var forceSave = cmd.getForceSave(); - var forceSaveType = forceSave ? forceSave.getType() : commonDefines.c_oAscForceSaveTypes.Command; - var isSfcmSuccess = false; - var statusOk; - var statusErr; - if (isSfcm) { - statusOk = docsCoServer.c_oAscServerStatus.MustSaveForce; - statusErr = docsCoServer.c_oAscServerStatus.CorruptedForce; - } else { - statusOk = docsCoServer.c_oAscServerStatus.MustSave; - statusErr = docsCoServer.c_oAscServerStatus.Corrupted; - } - if (getRes) { - logger.debug('Callback commandSfcCallback: docId = %s callback = %s', docId, getRes.server.href); - var outputSfc = new commonDefines.OutputSfcData(); - outputSfc.setKey(docId); - var users = []; - if (cmd.getUserId()) { - users.push(cmd.getUserId()); - } - outputSfc.setUsers(users); - if (!isSfcm) { - var actions = []; - //use UserId case UserActionId miss in gc convertion - var userActionId = cmd.getUserActionId() || cmd.getUserId(); - if (userActionId) { - actions.push(new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, userActionId)); - } - outputSfc.setActions(actions); - } - outputSfc.setUserData(cmd.getUserData()); - if (!isError || isErrorCorrupted) { - try { - var data = yield storage.getObject(savePathHistory); - outputSfc.setChangeHistory(JSON.parse(data.toString('utf-8'))); - outputSfc.setUrl(yield storage.getSignedUrl(getRes.baseUrl, savePathDoc)); - outputSfc.setChangeUrl(yield storage.getSignedUrl(getRes.baseUrl, savePathChanges)); - } catch (e) { - logger.error('Error commandSfcCallback: docId = %s\r\n%s', docId, e.stack); - } - if (outputSfc.getUrl() && outputSfc.getUsers().length > 0) { - outputSfc.setStatus(statusOk); - } else { - isError = true; + //setUserId - set from changes in convert + //setUserActionId - used in case of save without changes(forgotten files) + const userLastChangeId = cmd.getUserId() || cmd.getUserActionId(); + const userLastChangeIndex = cmd.getUserIndex() || cmd.getUserActionIndex(); + let replyStr; + if (constants.EDITOR_CHANGES !== statusInfo || isSfcm) { + var saveKey = docId + cmd.getSaveKey(); + var isError = constants.NO_ERROR != statusInfo; + var isErrorCorrupted = constants.CONVERT_CORRUPTED == statusInfo; + var savePathDoc = saveKey + '/' + cmd.getOutputPath(); + var savePathChanges = saveKey + '/changes.zip'; + var savePathHistory = saveKey + '/changesHistory.json'; + var forceSave = cmd.getForceSave(); + var forceSaveType = forceSave ? forceSave.getType() : commonDefines.c_oAscForceSaveTypes.Command; + let forceSaveUserId = forceSave ? forceSave.getAuthorUserId() : undefined; + let forceSaveUserIndex = forceSave ? forceSave.getAuthorUserIndex() : undefined; + let callbackUserIndex = (forceSaveUserIndex || 0 === forceSaveUserIndex) ? forceSaveUserIndex : userLastChangeIndex; + let uri, baseUrl, wopiParams, lastOpenDate; + let selectRes = yield taskResult.select(ctx, docId); + let row = selectRes.length > 0 ? selectRes[0] : null; + if (row) { + if (row.callback) { + uri = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, callbackUserIndex); + wopiParams = wopiClient.parseWopiCallback(ctx, uri, row.callback); } + if (row.baseurl) { + baseUrl = row.baseurl; + } + lastOpenDate = row.last_open_date; } - if (isError) { - outputSfc.setStatus(statusErr); - } - var uri = getRes.server.href; + var isSfcmSuccess = false; + let storeForgotten = false; + let needRetry = false; + var statusOk; + var statusErr; if (isSfcm) { - var selectRes = yield taskResult.select(docId); - var row = selectRes.length > 0 ? selectRes[0] : null; - //send only if FileStatus.Ok to prevent forcesave after final save - if (row && row.status == taskResult.FileStatus.Ok) { - if (forceSave) { - outputSfc.setForceSaveType(forceSaveType); - outputSfc.setLastSave(new Date(forceSave.getTime()).toISOString()); + statusOk = docsCoServer.c_oAscServerStatus.MustSaveForce; + statusErr = docsCoServer.c_oAscServerStatus.CorruptedForce; + } else { + statusOk = docsCoServer.c_oAscServerStatus.MustSave; + statusErr = docsCoServer.c_oAscServerStatus.Corrupted; + } + let recoverTask = new taskResult.TaskResultData(); + recoverTask.status = commonDefines.FileStatus.Ok; + recoverTask.statusInfo = constants.NO_ERROR; + let updateIfTask = new taskResult.TaskResultData(); + updateIfTask.status = commonDefines.FileStatus.UpdateVersion; + updateIfTask.statusInfo = Math.floor(Date.now() / 60000);//minutes + let updateIfRes; + + let updateMask = new taskResult.TaskResultData(); + updateMask.tenant = ctx.tenant; + updateMask.key = docId; + if (row) { + if (isEncrypted) { + recoverTask.status = updateMask.status = row.status; + recoverTask.statusInfo = updateMask.statusInfo = row.status_info; + } else if ((commonDefines.FileStatus.SaveVersion === row.status && cmd.getStatusInfoIn() === row.status_info) || + commonDefines.FileStatus.UpdateVersion === row.status) { + if (commonDefines.FileStatus.UpdateVersion === row.status) { + updateIfRes = {affectedRows: 1}; } + recoverTask.status = commonDefines.FileStatus.SaveVersion; + recoverTask.statusInfo = cmd.getStatusInfoIn(); + updateMask.status = row.status; + updateMask.statusInfo = row.status_info; + } else { + updateIfRes = {affectedRows: 0}; + } + } else { + isError = true; + } + let outputSfc; + if (uri && baseUrl && userLastChangeId) { + ctx.logger.debug('Callback commandSfcCallback: callback = %s', uri); + outputSfc = new commonDefines.OutputSfcData(docId); + outputSfc.setEncrypted(isEncrypted); + var users = []; + let isOpenFromForgotten = false; + if (userLastChangeId) { + users.push(userLastChangeId); + } + outputSfc.setUsers(users); + if (!isSfcm) { + var actions = []; + //use UserId case UserActionId miss in gc convertion + var userActionId = cmd.getUserActionId() || cmd.getUserId(); + if (userActionId) { + actions.push(new commonDefines.OutputAction(commonDefines.c_oAscUserAction.Out, userActionId)); + } + outputSfc.setActions(actions); + } else if(forceSaveUserId) { + outputSfc.setActions([new commonDefines.OutputAction(commonDefines.c_oAscUserAction.ForceSaveButton, forceSaveUserId)]); + } + outputSfc.setUserData(cmd.getUserData()); + let formsData = cmd.getFormData(); + if (formsData) { + let formsDataPath = saveKey + '/formsdata.json'; + let formsBuffer = Buffer.from(JSON.stringify(formsData), 'utf8'); + yield storage.putObject(ctx, formsDataPath, formsBuffer, formsBuffer.length); + let formsDataUrl = yield storage.getSignedUrl(ctx, baseUrl, formsDataPath, commonDefines.c_oAscUrlTypes.Temporary); + outputSfc.setFormsDataUrl(formsDataUrl); + } + if (!isError || isErrorCorrupted) { try { - yield* docsCoServer.sendServerRequest(docId, uri, outputSfc); - isSfcmSuccess = true; - } catch (err) { - logger.error('sendServerRequest error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, outputSfc, err.stack); + let forgotten = yield storage.listObjects(ctx, docId, tenForgottenFiles); + let isSendHistory = 0 === forgotten.length; + if (!isSendHistory) { + //check indicator file to determine if opening was from the forgotten file + var forgottenMarkPath = docId + '/' + tenForgottenFilesName + '.txt'; + var forgottenMark = yield storage.listObjects(ctx, forgottenMarkPath); + isOpenFromForgotten = 0 !== forgottenMark.length; + isSendHistory = !isOpenFromForgotten; + ctx.logger.debug('commandSfcCallback forgotten no empty: isSendHistory = %s', isSendHistory); + } + if (isSendHistory && !isEncrypted) { + //don't send history info because changes isn't from file in storage + var data = yield storage.getObject(ctx, savePathHistory); + outputSfc.setChangeHistory(JSON.parse(data.toString('utf-8'))); + let changeUrl = yield storage.getSignedUrl(ctx, baseUrl, savePathChanges, + commonDefines.c_oAscUrlTypes.Temporary); + outputSfc.setChangeUrl(changeUrl); + } else { + //for backward compatibility. remove this when Community is ready + outputSfc.setChangeHistory({}); + } + let url = yield storage.getSignedUrl(ctx, baseUrl, savePathDoc, commonDefines.c_oAscUrlTypes.Temporary); + outputSfc.setUrl(url); + outputSfc.setExtName(pathModule.extname(savePathDoc)); + } catch (e) { + ctx.logger.error('Error commandSfcCallback: %s', e.stack); + } + if (outputSfc.getUrl() && outputSfc.getUsers().length > 0) { + outputSfc.setStatus(statusOk); + } else { + isError = true; } } - } else { - //if anybody in document stop save - var hasEditors = yield* docsCoServer.hasEditors(docId); - logger.debug('hasEditors commandSfcCallback: docId = %s hasEditors = %d', docId, hasEditors); - if (!hasEditors) { - let lastSave = yield* docsCoServer.getLastSave(docId); - let notModified = yield* docsCoServer.getLastForceSave(docId, lastSave); - var lastSaveDate = lastSave ? new Date(lastSave.time) : new Date(); - outputSfc.setLastSave(lastSaveDate.toISOString()); - outputSfc.setNotModified(notModified); - var updateMask = new taskResult.TaskResultData(); - updateMask.key = docId; - updateMask.status = taskResult.FileStatus.SaveVersion; - updateMask.statusInfo = cmd.getData(); - var updateIfTask = new taskResult.TaskResultData(); - updateIfTask.status = taskResult.FileStatus.UpdateVersion; - updateIfTask.statusInfo = constants.NO_ERROR; - var updateIfRes = yield taskResult.updateIf(updateIfTask, updateMask); - if (updateIfRes.affectedRows > 0) { - var replyStr = null; - try { - replyStr = yield* docsCoServer.sendServerRequest(docId, uri, outputSfc); - } catch (err) { - replyStr = null; - logger.error('sendServerRequest error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, outputSfc, err.stack); + if (isError) { + outputSfc.setStatus(statusErr); + } + if (isSfcm) { + let selectRes = yield taskResult.select(ctx, docId); + let row = selectRes.length > 0 ? selectRes[0] : null; + //send only if FileStatus.Ok to prevent forcesave after final save + if (row && row.status == commonDefines.FileStatus.Ok) { + if (forceSave) { + let forceSaveDate = forceSave.getTime() ? new Date(forceSave.getTime()): new Date(); + outputSfc.setForceSaveType(forceSaveType); + outputSfc.setLastSave(forceSaveDate.toISOString()); + } + if (forceSave && forceSaveType === commonDefines.c_oAscForceSaveTypes.Internal) { + //send to browser only if internal forcesave + isSfcmSuccess = true; + } else { + try { + if (wopiParams) { + if (outputSfc.getUrl()) { + if (forceSaveType === commonDefines.c_oAscForceSaveTypes.Form) { + yield processWopiSaveAs(ctx, cmd); + replyStr = JSON.stringify({error: 0}); + } else { + let isAutoSave = forceSaveType !== commonDefines.c_oAscForceSaveTypes.Button && forceSaveType !== commonDefines.c_oAscForceSaveTypes.Form; + replyStr = yield processWopiPutFile(ctx, docId, wopiParams, savePathDoc, userLastChangeId, true, isAutoSave, false); + } + } else { + replyStr = JSON.stringify({error: 1, descr: "wopi: no file"}); + } + } else { + replyStr = yield docsCoServer.sendServerRequest(ctx, uri, outputSfc, checkAndFixAuthorizationLength); + } + let replyData = docsCoServer.parseReplyData(ctx, replyStr); + isSfcmSuccess = replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error; + if (replyData && commonDefines.c_oAscServerCommandErrors.NoError != replyData.error) { + ctx.logger.warn('sendServerRequest returned an error: data = %s', replyStr); + } + } catch (err) { + ctx.logger.error('sendServerRequest error: url = %s;data = %j %s', uri, outputSfc, err.stack); + } } - var requestRes = false; - var replyData = docsCoServer.parseReplyData(docId, replyStr); - if (replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error) { - //в Ñлучае comunity server придет Ð·Ð°Ð¿Ñ€Ð¾Ñ Ð² CommandService проверÑем результат - var multi = redisClient.multi([ - ['get', redisKeySaved + docId], - ['del', redisKeySaved + docId] - ]); - var execRes = yield utils.promiseRedis(multi, multi.exec); - var savedVal = execRes[0]; - requestRes = (null == savedVal || '1' === savedVal); + } + } else { + //if anybody in document stop save + let editorsCount = yield docsCoServer.getEditorsCountPromise(ctx, docId); + ctx.logger.debug('commandSfcCallback presence: count = %d', editorsCount); + if (0 === editorsCount || (isEncrypted && 1 === editorsCount)) { + if (!updateIfRes) { + updateIfRes = yield taskResult.updateIf(ctx, updateIfTask, updateMask); } - if (requestRes) { - yield docsCoServer.cleanDocumentOnExitPromise(docId, true); + if (updateIfRes.affectedRows > 0) { + let actualForceSave = yield docsCoServer.editorData.getForceSave(ctx, docId); + let forceSaveDate = (actualForceSave && actualForceSave.time) ? new Date(actualForceSave.time) : new Date(); + let notModified = actualForceSave && true === actualForceSave.ended; + outputSfc.setLastSave(forceSaveDate.toISOString()); + outputSfc.setNotModified(notModified); + + updateMask.status = updateIfTask.status; + updateMask.statusInfo = updateIfTask.statusInfo; + try { + if (wopiParams) { + if (outputSfc.getUrl()) { + replyStr = yield processWopiPutFile(ctx, docId, wopiParams, savePathDoc, userLastChangeId, !notModified, false, true); + } else { + replyStr = JSON.stringify({error: 1, descr: "wopi: no file"}); + } + } else { + replyStr = yield docsCoServer.sendServerRequest(ctx, uri, outputSfc, checkAndFixAuthorizationLength); + } + } catch (err) { + ctx.logger.error('sendServerRequest error: url = %s;data = %j %s', uri, outputSfc, err.stack); + const retryHttpStatus = new MultiRange(tenCallbackBackoffOptions.httpStatus); + if (!isEncrypted && !docsCoServer.getIsShutdown() && (!err.statusCode || retryHttpStatus.has(err.statusCode.toString()))) { + let attempt = cmd.getAttempt() || 0; + if (attempt < tenCallbackBackoffOptions.retries) { + needRetry = true; + } else { + ctx.logger.warn('commandSfcCallback backoff limit exceeded'); + } + } + } + var requestRes = false; + var replyData = docsCoServer.parseReplyData(ctx, replyStr); + if (replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error) { + //in the case of a community server, a request will come to the Command Service, check the result + var savedVal = yield docsCoServer.editorData.getdelSaved(ctx, docId); + requestRes = (null == savedVal || '1' === savedVal); + } + if (replyData && commonDefines.c_oAscServerCommandErrors.NoError != replyData.error) { + ctx.logger.warn('sendServerRequest returned an error: data = %s', replyStr); + } + if (requestRes) { + updateIfTask = undefined; + yield docsCoServer.cleanDocumentOnExitPromise(ctx, docId, true, callbackUserIndex); + if (isOpenFromForgotten) { + //remove forgotten file in cache + yield cleanupCache(ctx, docId); + } + if (lastOpenDate) { + //todo error case + let time = new Date() - lastOpenDate; + ctx.logger.debug('commandSfcCallback saveAfterEditingSessionClosed=%d', time); + if (clientStatsD) { + clientStatsD.timing('coauth.saveAfterEditingSessionClosed', time); + } + } + } else { + storeForgotten = true; + } } else { - var updateTask = new taskResult.TaskResultData(); - updateTask.key = docId; - updateTask.status = taskResult.FileStatus.Ok; - updateTask.statusInfo = constants.NO_ERROR; - yield taskResult.update(updateTask); + updateIfTask = undefined; } } } + } else { + ctx.logger.warn('Empty Callback=%s or baseUrl=%s or userLastChangeId=%s commandSfcCallback', uri, baseUrl, userLastChangeId); + storeForgotten = true; + } + if (undefined !== updateIfTask && !isSfcm) { + ctx.logger.debug('commandSfcCallback restore %d status', recoverTask.status); + updateIfTask.status = recoverTask.status; + updateIfTask.statusInfo = recoverTask.statusInfo; + updateIfRes = yield taskResult.updateIf(ctx, updateIfTask, updateMask); + if (updateIfRes.affectedRows > 0) { + updateMask.status = updateIfTask.status; + updateMask.statusInfo = updateIfTask.statusInfo; + } else { + ctx.logger.debug('commandSfcCallback restore %d status failed', recoverTask.status); + } + } + if (storeForgotten && !needRetry && !isEncrypted && (!isError || isErrorCorrupted)) { + try { + ctx.logger.warn("storeForgotten"); + let forgottenName = tenForgottenFilesName + pathModule.extname(cmd.getOutputPath()); + yield storage.copyObject(ctx, savePathDoc, docId + '/' + forgottenName, undefined, tenForgottenFiles); + } catch (err) { + ctx.logger.error('Error storeForgotten: %s', err.stack); + } + if (!isSfcm) { + //todo simultaneous opening + //clean redis (redisKeyPresenceSet and redisKeyPresenceHash removed with last element) + yield docsCoServer.editorData.cleanDocumentOnExit(ctx, docId); + //to unlock wopi file + yield docsCoServer.unlockWopiDoc(ctx, docId, callbackUserIndex); + //cleanupRes can be false in case of simultaneous opening. it is OK + let cleanupRes = yield cleanupCacheIf(ctx, updateMask); + ctx.logger.debug('storeForgotten cleanupRes=%s', cleanupRes); + } + } + if (forceSave) { + yield* docsCoServer.setForceSave(ctx, docId, forceSave, cmd, isSfcmSuccess && !isError, outputSfc?.getUrl()); + } + if (needRetry) { + let attempt = cmd.getAttempt() || 0; + cmd.setAttempt(attempt + 1); + let queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); + queueData.setCmd(cmd); + let timeout = retry.createTimeout(attempt, tenCallbackBackoffOptions.timeout); + ctx.logger.debug('commandSfcCallback backoff timeout = %d', timeout); + yield* docsCoServer.addDelayed(queueData, timeout); } } else { - logger.error('Empty Callback commandSfcCallback: docId = %s', docId); - } - if (forceSave) { - yield* docsCoServer.setForceSave(docId, forceSave, cmd, isSfcmSuccess && !isError); + ctx.logger.debug('commandSfcCallback cleanDocumentOnExitNoChangesPromise'); + yield docsCoServer.cleanDocumentOnExitNoChangesPromise(ctx, docId, undefined, userLastChangeIndex, true); } + if ((docsCoServer.getIsShutdown() && !isSfcm) || cmd.getRedisKey()) { let keyRedis = cmd.getRedisKey() ? cmd.getRedisKey() : redisKeyShutdown; - yield utils.promiseRedis(redisClient, redisClient.srem, keyRedis, docId); + yield docsCoServer.editorStat.removeShutdown(keyRedis, docId); } - logger.debug('End commandSfcCallback: docId = %s', docId); + ctx.logger.debug('End commandSfcCallback'); + return replyStr; +}); +function* processWopiPutFile(ctx, docId, wopiParams, savePathDoc, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) { + let res = '{"error": 1}'; + let metadata = yield storage.headObject(ctx, savePathDoc); + let streamObj = yield storage.createReadStream(ctx, savePathDoc); + let postRes = yield wopiClient.putFile(ctx, wopiParams, null, streamObj.readStream, metadata.ContentLength, userLastChangeId, isModifiedByUser, isAutosave, isExitSave); + if (postRes) { + res = '{"error": 0}'; + let body = wopiClient.parsePutFileResponse(ctx, postRes); + //collabora nexcloud connector + if (body?.LastModifiedTime) { + let lastModifiedTimeInfo = wopiClient.getWopiModifiedMarker(wopiParams, body.LastModifiedTime); + yield commandOpenStartPromise(ctx, docId, undefined, lastModifiedTimeInfo); + } + } + return res; } -function* commandSendMMCallback(cmd) { +function* commandSendMMCallback(ctx, cmd) { var docId = cmd.getDocId(); - logger.debug('Start commandSendMMCallback: docId = %s', docId); - var saveKey = cmd.getSaveKey(); + ctx.logger.debug('Start commandSendMMCallback'); + var saveKey = docId + cmd.getSaveKey(); var statusInfo = cmd.getStatusInfo(); - var outputSfc = new commonDefines.OutputSfcData(); - outputSfc.setKey(docId); + var outputSfc = new commonDefines.OutputSfcData(docId); if (constants.NO_ERROR == statusInfo) { outputSfc.setStatus(docsCoServer.c_oAscServerStatus.MailMerge); } else { @@ -672,7 +1311,7 @@ function* commandSendMMCallback(cmd) { var outputMailMerge = new commonDefines.OutputMailMerge(mailMergeSendData); outputSfc.setMailMerge(outputMailMerge); outputSfc.setUsers([mailMergeSendData.getUserId()]); - var data = yield storage.getObject(saveKey + '/' + cmd.getOutputPath()); + var data = yield storage.getObject(ctx, saveKey + '/' + cmd.getOutputPath()); var xml = data.toString('utf8'); var files = xml.match(/[< ]file.*?\/>/g); var recordRemain = (mailMergeSendData.getRecordTo() - mailMergeSendData.getRecordFrom() + 1); @@ -683,72 +1322,88 @@ function* commandSendMMCallback(cmd) { outputMailMerge.setTo(fieldRes[1]); outputMailMerge.setRecordIndex(recordIndexStart + i); var pathRes = /path=["'](.*?)["']/.exec(file); - var signedUrl = yield storage.getSignedUrl(mailMergeSendData.getBaseUrl(), saveKey + '/' + pathRes[1]); + var signedUrl = yield storage.getSignedUrl(ctx, mailMergeSendData.getBaseUrl(), saveKey + '/' + pathRes[1], + commonDefines.c_oAscUrlTypes.Temporary); outputSfc.setUrl(signedUrl); + outputSfc.setExtName(pathModule.extname(pathRes[1])); var uri = mailMergeSendData.getUrl(); var replyStr = null; try { - replyStr = yield* docsCoServer.sendServerRequest(docId, uri, outputSfc); + replyStr = yield docsCoServer.sendServerRequest(ctx, uri, outputSfc); } catch (err) { replyStr = null; - logger.error('sendServerRequest error: docId = %s;url = %s;data = %j\r\n%s', docId, uri, outputSfc, err.stack); + ctx.logger.error('sendServerRequest error: url = %s;data = %j %s', uri, outputSfc, err.stack); } - var replyData = docsCoServer.parseReplyData(docId, replyStr); + var replyData = docsCoServer.parseReplyData(ctx, replyStr); if (!(replyData && commonDefines.c_oAscServerCommandErrors.NoError == replyData.error)) { var recordErrorCount = mailMergeSendData.getRecordErrorCount(); recordErrorCount++; outputMailMerge.setRecordErrorCount(recordErrorCount); mailMergeSendData.setRecordErrorCount(recordErrorCount); } + if (replyData && commonDefines.c_oAscServerCommandErrors.NoError != replyData.error) { + ctx.logger.warn('sendServerRequest returned an error: data = %s', docId, replyStr); + } } var newRecordFrom = mailMergeSendData.getRecordFrom() + Math.max(files.length, 1); if (newRecordFrom <= mailMergeSendData.getRecordTo()) { mailMergeSendData.setRecordFrom(newRecordFrom); - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); } else { - logger.debug('End MailMerge: docId = %s', docId); + ctx.logger.debug('End MailMerge'); } - logger.debug('End commandSendMMCallback: docId = %s', docId); + ctx.logger.debug('End commandSendMMCallback'); } -exports.openDocument = function(conn, cmd, opt_upsertRes) { +exports.openDocument = function(ctx, conn, cmd, opt_upsertRes, opt_bIsRestore) { return co(function* () { var outputData; - var docId = conn ? conn.docId : 'null'; try { var startDate = null; if(clientStatsD) { startDate = new Date(); } - logger.debug('Start command: docId = %s %s', docId, JSON.stringify(cmd)); + ctx.logger.debug('Start command: %s', JSON.stringify(cmd)); outputData = new OutputData(cmd.getCommand()); + let res = true; switch (cmd.getCommand()) { case 'open': - yield* commandOpen(conn, cmd, outputData, opt_upsertRes); + yield* commandOpen(ctx, conn, cmd, outputData, opt_upsertRes, opt_bIsRestore); break; case 'reopen': - yield* commandReopen(cmd); + res = yield* commandReopen(ctx, conn, cmd, outputData); break; - case 'imgurl': case 'imgurls': - yield* commandImgurls(conn, cmd, outputData); + yield* commandImgurls(ctx, conn, cmd, outputData); break; case 'pathurl': - yield* commandPathUrl(conn, cmd, outputData); + yield* commandPathUrl(ctx, conn, cmd, outputData); + break; + case 'pathurls': + yield* commandPathUrls(ctx, conn, cmd.getData(), outputData); + break; + case 'setpassword': + yield* commandSetPassword(ctx, conn, cmd, outputData); + break; + case 'changedocinfo': + yield* commandChangeDocInfo(ctx, conn, cmd, outputData); break; default: + res = false; + break; + } + if(!res){ outputData.setStatus('err'); outputData.setData(constants.UNKNOWN); - break; } if(clientStatsD) { clientStatsD.timing('coauth.openDocument.' + cmd.getCommand(), new Date() - startDate); } } catch (e) { - logger.error('Error openDocument: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error openDocument: %s', e.stack); if (!outputData) { outputData = new OutputData(); } @@ -756,62 +1411,84 @@ exports.openDocument = function(conn, cmd, opt_upsertRes) { outputData.setData(constants.UNKNOWN); } finally { - if (outputData && outputData.getStatus()) { - logger.debug('Response command: docId = %s %s', docId, JSON.stringify(outputData)); - docsCoServer.sendData(conn, new OutputDataWrap('documentOpen', outputData)); + if (outputData?.getStatus()) { + ctx.logger.debug('Response command: %s', JSON.stringify(outputData)); + docsCoServer.sendData(ctx, conn, new OutputDataWrap('documentOpen', outputData)); } - logger.debug('End command: docId = %s', docId); + ctx.logger.debug('End command'); } }); }; exports.downloadAs = function(req, res) { return co(function* () { var docId = 'null'; + let ctx = new operationContext.Context(); try { var startDate = null; if(clientStatsD) { startDate = new Date(); } + ctx.initFromRequest(req); + yield ctx.initTenantCache(); var strCmd = req.query['cmd']; var cmd = new commonDefines.InputCommand(JSON.parse(strCmd)); docId = cmd.getDocId(); - logger.debug('Start downloadAs: docId = %s %s', docId, strCmd); + ctx.setDocId(docId); + ctx.logger.debug('Start downloadAs: %s', strCmd); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); - if (cfgTokenEnableBrowser) { + if (tenTokenEnableBrowser) { var isValidJwt = false; - var checkJwtRes = docsCoServer.checkJwt(docId, cmd.getJwt(), true); - if (checkJwtRes.decoded) { - var doc = checkJwtRes.decoded.document; - if (!doc.permissions || (false !== doc.permissions.download || false !== doc.permissions.print)) { + if (cmd.getTokenDownload()) { + let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenDownload(), commonDefines.c_oAscSecretType.Browser); + if (checkJwtRes.decoded) { isValidJwt = true; - docId = doc.key; - cmd.setDocId(doc.key); + cmd.setFormat(checkJwtRes.decoded.fileType); + cmd.setUrl(checkJwtRes.decoded.url); + cmd.setWithAuthorization(true); } else { - logger.error('Error downloadAs jwt: docId = %s\r\n%s', docId, 'access deny'); + ctx.logger.warn('Error downloadAs jwt: %s', checkJwtRes.description); } } else { - logger.error('Error downloadAs jwt: docId = %s\r\n%s', docId, checkJwtRes.description); + let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session); + if (checkJwtRes.decoded) { + let decoded = checkJwtRes.decoded; + var doc = checkJwtRes.decoded.document; + if (!doc.permissions || (false !== doc.permissions.download || false !== doc.permissions.print)) { + isValidJwt = true; + docId = doc.key; + cmd.setDocId(doc.key); + cmd.setUserIndex(decoded.editorConfig && decoded.editorConfig.user && decoded.editorConfig.user.index); + } else { + ctx.logger.warn('Error downloadAs jwt: %s', 'access deny'); + } + } else { + ctx.logger.warn('Error downloadAs jwt: %s', checkJwtRes.description); + } } if (!isValidJwt) { - res.sendStatus(400); + res.sendStatus(403); return; } } - + ctx.setDocId(docId); + var selectRes = yield taskResult.select(ctx, docId); + var row = selectRes.length > 0 ? selectRes[0] : null; + if (!cmd.getWithoutPassword()) { + addPasswordToCmd(ctx, cmd, row && row.password, row && row.change_id); + } + addOriginFormat(ctx, cmd, row); cmd.setData(req.body); var outputData = new OutputData(cmd.getCommand()); switch (cmd.getCommand()) { case 'save': - yield* commandSave(cmd, outputData); + yield* commandSave(ctx, cmd, outputData); break; case 'savefromorigin': - yield* commandSaveFromOrigin(cmd, outputData); + yield* commandSaveFromOrigin(ctx, cmd, outputData, row && row.password); break; case 'sendmm': - yield* commandSendMailMerge(cmd, outputData); - break; - case 'sfct': - yield* commandSfct(cmd, outputData); + yield* commandSendMailMerge(ctx, cmd, outputData); break; default: outputData.setStatus('err'); @@ -819,52 +1496,335 @@ exports.downloadAs = function(req, res) { break; } var strRes = JSON.stringify(outputData); + res.setHeader('Content-Type', 'application/json'); res.send(strRes); - logger.debug('End downloadAs: docId = %s %s', docId, strRes); + ctx.logger.debug('End downloadAs: %s', strRes); if(clientStatsD) { clientStatsD.timing('coauth.downloadAs.' + cmd.getCommand(), new Date() - startDate); } } catch (e) { - logger.error('Error downloadAs: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error downloadAs: %s', e.stack); res.sendStatus(400); } }); }; -exports.saveFromChanges = function(docId, statusInfo, optFormat, opt_userId, opt_queue) { +exports.saveFile = function(req, res) { + return co(function*() { + let docId = 'null'; + let ctx = new operationContext.Context(); + try { + let startDate = null; + if (clientStatsD) { + startDate = new Date(); + } + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + let strCmd = req.query['cmd']; + let cmd = new commonDefines.InputCommand(JSON.parse(strCmd)); + docId = cmd.getDocId(); + ctx.setDocId(docId); + ctx.logger.debug('Start saveFile'); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + + if (tenTokenEnableBrowser) { + let isValidJwt = false; + let checkJwtRes = yield docsCoServer.checkJwt(ctx, cmd.getTokenSession(), commonDefines.c_oAscSecretType.Session); + if (checkJwtRes.decoded) { + let doc = checkJwtRes.decoded.document; + var edit = checkJwtRes.decoded.editorConfig; + if (doc.ds_encrypted && !edit.ds_view && !edit.ds_isCloseCoAuthoring) { + isValidJwt = true; + docId = doc.key; + cmd.setDocId(doc.key); + } else { + ctx.logger.warn('Error saveFile jwt: %s', 'access deny'); + } + } else { + ctx.logger.warn('Error saveFile jwt: %s', checkJwtRes.description); + } + if (!isValidJwt) { + res.sendStatus(403); + return; + } + } + ctx.setDocId(docId); + cmd.setStatusInfo(constants.NO_ERROR); + yield* addRandomKeyTaskCmd(ctx, cmd); + cmd.setOutputPath(constants.OUTPUT_NAME + pathModule.extname(cmd.getOutputPath())); + yield storage.putObject(ctx, docId + cmd.getSaveKey() + '/' + cmd.getOutputPath(), req.body, req.body.length); + let replyStr = yield commandSfcCallback(ctx, cmd, false, true); + if (replyStr) { + utils.fillResponseSimple(res, replyStr, 'application/json'); + } else { + res.sendStatus(400); + } + ctx.logger.debug('End saveFile: %s', replyStr); + if (clientStatsD) { + clientStatsD.timing('coauth.saveFile', new Date() - startDate); + } + } + catch (e) { + ctx.logger.error('Error saveFile: %s', e.stack); + res.sendStatus(400); + } + }); +}; +function getPrintFileUrl(ctx, docId, baseUrl, filename) { + return co(function*() { + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenTokenSessionAlgorithm = ctx.getCfg('services.CoAuthoring.token.session.algorithm', cfgTokenSessionAlgorithm); + const tenTokenSessionExpires = ms(ctx.getCfg('services.CoAuthoring.token.session.expires', cfgTokenSessionExpires)); + + baseUrl = utils.checkBaseUrl(ctx, baseUrl); + let token = ''; + if (tenTokenEnableBrowser) { + let payload = {document: {key: docId}}; + token = yield docsCoServer.signToken(ctx, payload, tenTokenSessionAlgorithm, tenTokenSessionExpires / 1000, commonDefines.c_oAscSecretType.Session); + } + //while save printed file Chrome's extension seems to rely on the resource name set in the URI https://stackoverflow.com/a/53593453 + //replace '/' with %2f before encodeURIComponent becase nginx determine %2f as '/' and get wrong system path + let userFriendlyName = encodeURIComponent(filename.replace(/\//g, "%2f")); + let res = `${baseUrl}/printfile/${encodeURIComponent(docId)}/${userFriendlyName}?token=${encodeURIComponent(token)}`; + if (ctx.shardKey) { + res += `&${constants.SHARD_KEY_API_NAME}=${encodeURIComponent(ctx.shardKey)}`; + } + if (ctx.wopiSrc) { + res += `&${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(ctx.wopiSrc)}`; + } + res += `&filename=${userFriendlyName}`; + return res; + }); +} +exports.getPrintFileUrl = getPrintFileUrl; +exports.printFile = function(req, res) { + return co(function*() { + let docId = 'null'; + let ctx = new operationContext.Context(); + try { + let startDate = null; + if (clientStatsD) { + startDate = new Date(); + } + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + let filename = req.query['filename']; + let token = req.query['token']; + docId = req.params.docid; + ctx.setDocId(docId); + ctx.logger.info('Start printFile'); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + + if (tenTokenEnableBrowser) { + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session); + if (checkJwtRes.decoded) { + let docIdBase = checkJwtRes.decoded.document.key; + if (!docId.startsWith(docIdBase)) { + ctx.logger.warn('Error printFile jwt: description = %s', 'access deny'); + res.sendStatus(403); + return; + } + } else { + ctx.logger.warn('Error printFile jwt: description = %s', checkJwtRes.description); + res.sendStatus(403); + return; + } + } + ctx.setDocId(docId); + let streamObj = yield storage.createReadStream(ctx, `${docId}/${constants.OUTPUT_NAME}.pdf`); + res.setHeader('Content-Disposition', utils.getContentDisposition(filename, null, constants.CONTENT_DISPOSITION_INLINE)); + res.setHeader('Content-Length', streamObj.contentLength); + res.setHeader('Content-Type', 'application/pdf'); + yield utils.pipeStreams(streamObj.readStream, res, true); + + if (clientStatsD) { + clientStatsD.timing('coauth.printFile', new Date() - startDate); + } + } + catch (e) { + ctx.logger.error('Error printFile: %s', e.stack); + res.sendStatus(400); + } + finally { + ctx.logger.info('End printFile'); + } + }); +}; +exports.downloadFile = function(req, res) { + return co(function*() { + let ctx = new operationContext.Context(); + try { + let startDate = null; + if (clientStatsD) { + startDate = new Date(); + } + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.setDocId(req.params.docid); + //todo remove in 8.1. For compatibility + let url = req.get('x-url'); + if (url) { + url = decodeURI(url); + } + ctx.logger.info('Start downloadFile'); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenDownloadMaxBytes = ctx.getCfg('FileConverter.converter.maxDownloadBytes', cfgDownloadMaxBytes); + const tenDownloadTimeout = ctx.getCfg('FileConverter.converter.downloadTimeout', cfgDownloadTimeout); + const tenDownloadFileAllowExt = ctx.getCfg('services.CoAuthoring.server.downloadFileAllowExt', cfgDownloadFileAllowExt); + const tenNewFileTemplate = ctx.getCfg('services.CoAuthoring.server.newFileTemplate', cfgNewFileTemplate); + + let authorization; + let isInJwtToken = false; + let errorDescription; + let headers, fromTemplate; + let authRes = yield docsCoServer.getRequestParams(ctx, req); + if (authRes.code === constants.NO_ERROR) { + let decoded = authRes.params; + if (decoded.changesUrl) { + url = decoded.changesUrl; + isInJwtToken = true; + } else if (decoded.document && -1 !== tenDownloadFileAllowExt.indexOf(decoded.document.fileType)) { + url = decoded.document.url; + isInJwtToken = true; + } else if (decoded.url && -1 !== tenDownloadFileAllowExt.indexOf(decoded.fileType)) { + url = decoded.url; + isInJwtToken = true; + } else if (wopiClient.isWopiJwtToken(decoded)) { + if (decoded.fileInfo.Size === 0) { + //editnew case + fromTemplate = pathModule.extname(decoded.fileInfo.BaseFileName).substring(1); + } else { + ({url, headers} = yield wopiClient.getWopiFileUrl(ctx, decoded.fileInfo, decoded.userAuth)); + let filterStatus = yield wopiClient.checkIpFilter(ctx, url); + if (0 === filterStatus) { + //todo false? (true because it passed checkIpFilter for wopi) + //todo use directIfIn + isInJwtToken = true; + } else { + errorDescription = 'access deny'; + } + } + } else if (!tenTokenEnableBrowser) { + //todo token required + if (decoded.url) { + url = decoded.url; + isInJwtToken = true; + } + } else { + errorDescription = 'access deny'; + } + } else { + errorDescription = authRes.description || 'need token'; + } + if (errorDescription) { + ctx.logger.warn('Error downloadFile jwt: description = %s', errorDescription); + res.sendStatus(403); + return; + } + if (fromTemplate) { + ctx.logger.debug('downloadFile from file template: %s', fromTemplate); + let locale = constants.TEMPLATES_DEFAULT_LOCALE; + let fileTemplatePath = pathModule.join(tenNewFileTemplate, locale, 'new.' + fromTemplate); + res.sendFile(pathModule.resolve(fileTemplatePath)); + } else { + if (utils.canIncludeOutboxAuthorization(ctx, url)) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + authorization = utils.fillJwtForRequest(ctx, {url: url}, secret, false); + } + let urlParsed = urlModule.parse(url); + let filterStatus = yield* utils.checkHostFilter(ctx, urlParsed.hostname); + if (0 !== filterStatus) { + ctx.logger.warn('Error downloadFile checkIpFilter error: url = %s', url); + res.sendStatus(filterStatus); + return; + } + + if (req.get('Range')) { + if (!headers) { + headers = {}; + } + headers['Range'] = req.get('Range'); + } + + yield utils.downloadUrlPromise(ctx, url, tenDownloadTimeout, tenDownloadMaxBytes, authorization, isInJwtToken, headers, res); + } + + if (clientStatsD) { + clientStatsD.timing('coauth.downloadFile', new Date() - startDate); + } + } + catch (err) { + if (err.code === "ERR_STREAM_PREMATURE_CLOSE") { + ctx.logger.debug('Error downloadFile: %s', err.stack); + } else { + ctx.logger.error('Error downloadFile: %s', err.stack); + //catch errors because status may be sent while piping to response + if (!res.headersSent) { + try { + if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') { + res.sendStatus(408); + } else if (err.code === 'EMSGSIZE') { + res.sendStatus(413); + } else if (err.response) { + res.sendStatus(err.response.statusCode); + } else { + res.sendStatus(400); + } + } catch (err) { + ctx.logger.error('Error downloadFile: %s', err.stack); + } + } + } + } + finally { + ctx.logger.info('End downloadFile'); + } + }); +}; +exports.saveFromChanges = function(ctx, docId, statusInfo, optFormat, opt_userId, opt_userIndex, opt_userLcid, opt_queue, opt_initShardKey) { return co(function* () { try { var startDate = null; if(clientStatsD) { startDate = new Date(); } - logger.debug('Start saveFromChanges: docId = %s', docId); - var task = new taskResult.TaskResultData(); - task.key = docId; - //делаем select, потому что за Ð²Ñ€ÐµÐ¼Ñ timeout Ð¸Ð½Ñ„Ð¾Ñ€Ð¼Ð°Ñ†Ð¸Ñ Ð¼Ð¾Ð³Ð»Ð° изменитьÑÑ - var selectRes = yield taskResult.select(docId); + ctx.logger.debug('Start saveFromChanges'); + //we do a select, because during the timeout the information could change + var selectRes = yield taskResult.select(ctx, docId); var row = selectRes.length > 0 ? selectRes[0] : null; - if (row && row.status == taskResult.FileStatus.SaveVersion && row.status_info == statusInfo) { + if (row && row.status == commonDefines.FileStatus.SaveVersion && row.status_info == statusInfo) { if (null == optFormat) { - optFormat = constants.AVS_OFFICESTUDIO_FILE_OTHER_TEAMLAB_INNER; + optFormat = changeFormatByOrigin(ctx, row, constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML); + } + if (opt_initShardKey) { + ctx.setShardKey(sqlBase.DocumentAdditional.prototype.getShardKey(row.additional)); + ctx.setWopiSrc(sqlBase.DocumentAdditional.prototype.getWopiSrc(row.additional)); } var cmd = new commonDefines.InputCommand(); cmd.setCommand('sfc'); cmd.setDocId(docId); cmd.setOutputFormat(optFormat); - cmd.setData(statusInfo); + cmd.setStatusInfoIn(statusInfo); cmd.setUserActionId(opt_userId); - yield* addRandomKeyTaskCmd(cmd); - var queueData = getSaveTask(cmd); + cmd.setUserActionIndex(opt_userIndex); + cmd.appendJsonParams(getOpenedAtJSONParams(row)); + //todo lang and region are different + cmd.setLCID(opt_userLcid); + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + cmd.setWopiParams(wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback)); + addPasswordToCmd(ctx, cmd, row && row.password, row && row.change_id); + addOriginFormat(ctx, cmd, row); + yield* addRandomKeyTaskCmd(ctx, cmd); + var queueData = getSaveTask(ctx, cmd); queueData.setFromChanges(true); yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_NORMAL, opt_queue); if (docsCoServer.getIsShutdown()) { - yield utils.promiseRedis(redisClient, redisClient.sadd, redisKeyShutdown, docId); + yield docsCoServer.editorStat.addShutdown(redisKeyShutdown, docId); } - logger.debug('AddTask saveFromChanges: docId = %s', docId); + ctx.logger.debug('AddTask saveFromChanges'); } else { if (row) { - logger.debug('saveFromChanges status mismatch: docId = %s; row: %d; %d; expected: %d', docId, row.status, row.status_info, statusInfo); + ctx.logger.debug('saveFromChanges status mismatch: row: %d; %d; expected: %d', row.status, row.status_info, statusInfo); } } if (clientStatsD) { @@ -872,61 +1832,111 @@ exports.saveFromChanges = function(docId, statusInfo, optFormat, opt_userId, opt } } catch (e) { - logger.error('Error saveFromChanges: docId = %s\r\n%s', docId, e.stack); + ctx.logger.error('Error saveFromChanges: %s', e.stack); } }); }; -exports.receiveTask = function(data, dataRaw) { + +async function processWopiSaveAs(ctx, cmd) { + let res; + const info = await docsCoServer.getCallback(ctx, cmd.getDocId(), cmd.getUserIndex()); + // info.wopiParams is null if it is not wopi + if (info?.wopiParams) { + const suggestedExt = `.${formatChecker.getStringFromFormat(cmd.getOutputFormat())}`; + const suggestedTarget = cmd.getSaveAsPath(); + const storageFilePath = `${cmd.getDocId()}${cmd.getSaveKey()}/${cmd.getOutputPath()}`; + const stream = await storage.createReadStream(ctx, storageFilePath); + const { wopiSrc, access_token } = info.wopiParams.userAuth; + res = await wopiClient.putRelativeFile(ctx, wopiSrc, access_token, null, stream.readStream, stream.contentLength, suggestedExt, suggestedTarget, false); + } + return {res: res, wopiParams: info?.wopiParams}; +} +exports.receiveTask = function(data, ack) { return co(function* () { - var docId = 'null'; + let ctx = new operationContext.Context(); try { var task = new commonDefines.TaskQueueData(JSON.parse(data)); if (task) { var cmd = task.getCmd(); - docId = cmd.getDocId(); - logger.debug('Start receiveTask: docId = %s %s', docId, data); - var updateTask = getUpdateResponse(cmd); - var updateRes = yield taskResult.update(updateTask); + ctx.initFromTaskQueueData(task); + yield ctx.initTenantCache(); + ctx.logger.info('receiveTask start: %s', data); + var updateTask = yield getUpdateResponse(ctx, cmd); + var updateRes = yield taskResult.update(ctx, updateTask); if (updateRes.affectedRows > 0) { var outputData = new OutputData(cmd.getCommand()); var command = cmd.getCommand(); - var additionalOutput = {needUrlKey: null, needUrlMethod: null}; - if ('open' == command || 'reopen' == command) { - //yield utils.sleep(5000); - yield* getOutputData(cmd, outputData, cmd.getDocId(), updateTask.status, - updateTask.statusInfo, null, additionalOutput); - } else if ('save' == command || 'savefromorigin' == command || 'sfct' == command) { - yield* getOutputData(cmd, outputData, cmd.getSaveKey(), updateTask.status, - updateTask.statusInfo, null, additionalOutput); - } else if ('sfcm' == command) { - yield* commandSfcCallback(cmd, true); - } else if ('sfc' == command) { - yield* commandSfcCallback(cmd, false); - } else if ('sendmm' == command) { - yield* commandSendMMCallback(cmd); - } else if ('conv' == command) { + var additionalOutput = {needUrlKey: null, needUrlMethod: null, needUrlType: null, + needUrlIsCorrectPassword: undefined, creationDate: undefined, openedAt: undefined, row: undefined}; + if ('open' === command || 'reopen' === command) { + yield getOutputData(ctx, cmd, outputData, cmd.getDocId(), null, additionalOutput); + //wopi from TemplateSource + if (additionalOutput.row) { + let row = additionalOutput.row; + let userAuthStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + let wopiParams = wopiClient.parseWopiCallback(ctx, userAuthStr, row.callback); + if (wopiParams?.commonInfo?.fileInfo?.TemplateSource) { + ctx.logger.debug('receiveTask: save document opened from TemplateSource'); + //todo + //no need to wait to open file faster + void docsCoServer.startForceSave(ctx, cmd.getDocId(), commonDefines.c_oAscForceSaveTypes.Timeout, + undefined, undefined, undefined, undefined, + undefined, undefined, undefined, row.baseurl, + undefined,undefined,undefined,undefined, + undefined,cmd.getExternalChangeInfo()); + } + } + } else if ('save' === command || 'savefromorigin' === command) { + let status = yield getOutputData(ctx, cmd, outputData, cmd.getDocId() + cmd.getSaveKey(), null, additionalOutput); + if (commonDefines.FileStatus.Ok === status && (cmd.getSaveAsPath() || cmd.getIsSaveAs())) { + //todo in case of wopi no need to send url. send it to avoid stubs in sdk + let saveAsRes = yield processWopiSaveAs(ctx, cmd); + if (!saveAsRes.res && saveAsRes.wopiParams) { + outputData.setStatus('err'); + outputData.setData(constants.CONVERT); + additionalOutput.needUrlKey = null; + } + } + } else if ('sfcm' === command) { + yield commandSfcCallback(ctx, cmd, true); + } else if ('sfc' === command) { + yield commandSfcCallback(ctx, cmd, false); + } else if ('sendmm' === command) { + yield* commandSendMMCallback(ctx, cmd); + } else if ('conv' === command) { //nothing } if (outputData.getStatus()) { - logger.debug('Send receiveTask: docId = %s %s', docId, JSON.stringify(outputData)); + ctx.logger.debug('receiveTask publish: %s', JSON.stringify(outputData)); var output = new OutputDataWrap('documentOpen', outputData); - yield* docsCoServer.publish({ - type: commonDefines.c_oPublishType.receiveTask, cmd: cmd, output: output, - needUrlKey: additionalOutput.needUrlKey, needUrlMethod: additionalOutput.needUrlMethod - }); + yield docsCoServer.publish(ctx, { + type: commonDefines.c_oPublishType.receiveTask, ctx: ctx, cmd: cmd, output: output, + needUrlKey: additionalOutput.needUrlKey, + needUrlMethod: additionalOutput.needUrlMethod, + needUrlType: additionalOutput.needUrlType, + needUrlIsCorrectPassword: additionalOutput.needUrlIsCorrectPassword, + creationDate: additionalOutput.creationDate, + openedAt: additionalOutput.openedAt + }); } } - yield* docsCoServer.removeResponse(dataRaw); - logger.debug('End receiveTask: docId = %s', docId); } } catch (err) { - logger.debug('Error receiveTask: docId = %s\r\n%s', docId, err.stack); + ctx.logger.error('receiveTask error: %s', err.stack); + } finally { + ctx.logger.info('receiveTask end'); + ack(); } }); }; exports.cleanupCache = cleanupCache; +exports.cleanupCacheIf = cleanupCacheIf; +exports.cleanupErrToReload = cleanupErrToReload; +exports.getOpenedAt = getOpenedAt; exports.commandSfctByCmd = commandSfctByCmd; exports.commandOpenStartPromise = commandOpenStartPromise; +exports.commandPathUrls = commandPathUrls; +exports.commandSfcCallback = commandSfcCallback; exports.OutputDataWrap = OutputDataWrap; exports.OutputData = OutputData; diff --git a/DocService/sources/changes2forgotten.js b/DocService/sources/changes2forgotten.js new file mode 100644 index 0000000000..e42b52699a --- /dev/null +++ b/DocService/sources/changes2forgotten.js @@ -0,0 +1,188 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; +var config = require('config'); +var configCoAuthoring = config.get('services.CoAuthoring'); +var co = require('co'); +var pubsubService = require('./pubsubRabbitMQ'); +var commonDefines = require('./../../Common/sources/commondefines'); +var constants = require('./../../Common/sources/constants'); +var utils = require('./../../Common/sources/utils'); +const storage = require('./../../Common/sources/storage-base'); +const queueService = require('./../../Common/sources/taskqueueRabbitMQ'); +const operationContext = require('./../../Common/sources/operationContext'); +const sqlBase = require('./databaseConnectors/baseConnector'); +const docsCoServer = require('./DocsCoServer'); +const taskResult = require('./taskresult'); +const cfgEditorDataStorage = config.get('services.CoAuthoring.server.editorDataStorage'); +const cfgEditorStatStorage = config.get('services.CoAuthoring.server.editorStatStorage'); +const editorStatStorage = require('./' + (cfgEditorStatStorage || cfgEditorDataStorage)); + +const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles'); +const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult'); + +var cfgRedisPrefix = configCoAuthoring.get('redis.prefix'); +var redisKeyShutdown = cfgRedisPrefix + constants.REDIS_KEY_SHUTDOWN; + +var WAIT_TIMEOUT = 30000; +var LOOP_TIMEOUT = 1000; +var EXEC_TIMEOUT = WAIT_TIMEOUT + utils.getConvertionTimeout(undefined); + +let addSqlParam = sqlBase.addSqlParameter; + +function updateDoc(ctx, docId, status, callback) { + return new Promise(function(resolve, reject) { + let values = []; + let p1 = addSqlParam(status, values); + let p2 = addSqlParam(callback, values); + let p3 = addSqlParam(ctx.tenant, values); + let p4 = addSqlParam(docId, values); + let sqlCommand = `UPDATE ${cfgTableResult} SET status=${p1},callback=${p2} WHERE tenant=${p3} AND id=${p4};`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }, undefined, undefined, values); + }); +} + +function shutdown() { + return co(function*() { + var res = true; + let ctx = new operationContext.Context(); + try { + let editorStat = editorStatStorage.EditorStat ? new editorStatStorage.EditorStat() : new editorStatStorage(); + ctx.logger.debug('shutdown start:' + EXEC_TIMEOUT); + + //redisKeyShutdown is not a simple counter, so it doesn't get decremented by a build that started before Shutdown started + //reset redisKeyShutdown just in case the previous run didn't finish yield editorData.cleanupShutdown(redisKeyShutdown); + let queue = new queueService(); + yield queue.initPromise(true, false, false, false, false, false); + + let pubsub = new pubsubService(); + yield pubsub.initPromise(); + //inner ping to update presence + ctx.logger.debug('shutdown pubsub shutdown message'); + yield pubsub.publish(JSON.stringify({type: commonDefines.c_oPublishType.shutdown, ctx: ctx, status: true})); + ctx.logger.debug('shutdown start wait pubsub deliver'); + yield utils.sleep(LOOP_TIMEOUT); + + let documentsWithChanges = yield sqlBase.getDocumentsWithChanges(ctx); + ctx.logger.debug('shutdown docs with changes count = %s', documentsWithChanges.length); + let docsWithEmptyForgotten = []; + let docsWithOutOfDateForgotten = []; + for (let i = 0; i < documentsWithChanges.length; ++i) { + let tenant = documentsWithChanges[i].tenant; + let docId = documentsWithChanges[i].id; + ctx.setTenant(tenant); + let forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles); + if (forgotten.length > 0) { + let selectRes = yield taskResult.select(ctx, docId); + if (selectRes.length > 0) { + let row = selectRes[0]; + if (commonDefines.FileStatus.SaveVersion !== row.status && commonDefines.FileStatus.UpdateVersion !== row.status){ + docsWithOutOfDateForgotten.push([tenant, docId]); + } + } + } else { + docsWithEmptyForgotten.push([tenant, docId]); + } + } + ctx.initDefault(); + ctx.logger.debug('shutdown docs with changes and empty forgotten count = %s', docsWithEmptyForgotten.length); + ctx.logger.debug('shutdown docs with changes and out of date forgotten count = %s', docsWithOutOfDateForgotten.length); + let docsToConvert = docsWithEmptyForgotten.concat(docsWithOutOfDateForgotten); + for (let i = 0; i < docsToConvert.length; ++i) { + let tenant = docsToConvert[i][0]; + let docId = docsToConvert[i][1]; + //todo refactor. group tenants? + ctx.setTenant(tenant); + yield ctx.initTenantCache(); + + yield updateDoc(ctx, docId, commonDefines.FileStatus.Ok, ""); + yield editorStat.addShutdown(redisKeyShutdown, docId); + ctx.logger.debug('shutdown createSaveTimerPromise %s', docId); + yield docsCoServer.createSaveTimer(ctx, docId, null, null, null, queue, true); + } + ctx.initDefault(); + //sleep because of bugs in createSaveTimerPromise + yield utils.sleep(LOOP_TIMEOUT); + + let startTime = new Date().getTime(); + while (true) { + let remainingFiles = yield editorStat.getShutdownCount(redisKeyShutdown); + ctx.logger.debug('shutdown remaining files:%d', remainingFiles); + let curTime = new Date().getTime() - startTime; + if (curTime >= EXEC_TIMEOUT || remainingFiles <= 0) { + if(curTime >= EXEC_TIMEOUT) { + ctx.logger.debug('shutdown timeout'); + } + break; + } + yield utils.sleep(LOOP_TIMEOUT); + } + let countInForgotten = 0; + for (let i = 0; i < docsToConvert.length; ++i) { + let tenant = docsToConvert[i][0]; + let docId = docsToConvert[i][1]; + ctx.setTenant(tenant); + let forgotten = yield storage.listObjects(ctx, docId, cfgForgottenFiles); + if (forgotten.length > 0) { + countInForgotten++; + } else { + ctx.logger.warn('shutdown missing in forgotten:%s', docId); + } + } + ctx.initDefault(); + ctx.logger.debug('shutdown docs placed in forgotten:%d', countInForgotten); + ctx.logger.debug('shutdown docs with unknown status:%d', docsToConvert.length - countInForgotten); + + //todo needs to check queues, because there may be long conversions running before Shutdown + //clean up + yield editorStat.cleanupShutdown(redisKeyShutdown); + yield pubsub.close(); + yield queue.close(); + + ctx.logger.debug('shutdown end'); + } catch (e) { + res = false; + ctx.logger.error('shutdown error:\r\n%s', e.stack); + } + process.exit(0); + return res; + }); +}; +exports.shutdown = shutdown; +shutdown(); diff --git a/DocService/sources/collectlost.js b/DocService/sources/collectlost.js deleted file mode 100644 index c2e95a6a68..0000000000 --- a/DocService/sources/collectlost.js +++ /dev/null @@ -1,113 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * Pursuant to Section 7(b) of the License you must retain the original Product - * logo when distributing the program. Pursuant to Section 7(e) we decline to - * grant you any rights under trademark law for use of our trademarks. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing 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 - * - */ - -'use strict'; -const config = require('config'); -const co = require('co'); -const logger = require('./../../Common/sources/logger'); -const sqlBase = require('./baseConnector'); -const converterService = require('./converterservice'); -const pubsubRedis = require('./pubsubRedis.js'); -const queueService = require('./../../Common/sources/taskqueueRabbitMQ'); -const constants = require('./../../Common/sources/constants'); -const utils = require('./../../Common/sources/utils'); - -const cfgRedisPrefix = config.get('services.CoAuthoring.redis.prefix'); -const redisKeyCollectLost = cfgRedisPrefix + constants.REDIS_KEY_COLLECT_LOST; - -const LOOP_TIMEOUT = 1000; -const EXEC_TIMEOUT = utils.CONVERTION_TIMEOUT; - -(function collectlost() { - return co(function*() { - let exitCode = 0; - let queue = null; - try { - logger.debug('collectlost start'); - - var redisClient = pubsubRedis.getClientRedis(); - - queue = new queueService(); - yield queue.initPromise(true, false, false, false); - - //collect documents without callback url - const selectRes = yield sqlBase.getEmptyCallbacks(); - - let docIds = []; - for (let i = 0; i < selectRes.length; ++i) { - docIds.push(selectRes[i].id); - } - logger.debug('collectlost docIds:%j', docIds); - if (docIds.length > 0) { - let multi = redisClient.multi([['sadd', redisKeyCollectLost].concat(docIds)]); - yield utils.promiseRedis(multi, multi.exec); - } - for (let i = 0; i < docIds.length; ++i) { - let docId = docIds[i]; - yield* converterService.convertFromChanges(docId, undefined, false, undefined, undefined, undefined, undefined, - queue, redisKeyCollectLost); - } - - logger.debug('collectlost start wait'); - const startTime = new Date().getTime(); - while (true) { - let curTime = new Date().getTime() - startTime; - if (curTime >= EXEC_TIMEOUT) { - exitCode = 1; - logger.debug('collectlost timeout'); - break; - } - const remainingFiles = yield utils.promiseRedis(redisClient, redisClient.scard, redisKeyCollectLost); - logger.debug('collectlost remaining files:%d', remainingFiles); - if (remainingFiles <= 0) { - break; - } - yield utils.sleep(LOOP_TIMEOUT); - } - //clean up - yield utils.promiseRedis(redisClient, redisClient.del, redisKeyCollectLost); - - logger.debug('collectlost end'); - } catch (e) { - logger.error('collectlost error:\r\n%s', e.stack); - } finally { - try { - if (queue) { - yield queue.close(); - } - } catch (e) { - logger.error('collectlost error:\r\n%s', e.stack); - } - process.exit(exitCode); - } - }); -})(); diff --git a/DocService/sources/converterservice.js b/DocService/sources/converterservice.js index 055b827946..0d9d9eafc3 100644 --- a/DocService/sources/converterservice.js +++ b/DocService/sources/converterservice.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,194 +32,307 @@ 'use strict'; +const path = require('path'); var config = require('config'); var co = require('co'); +const mime = require('mime'); var taskResult = require('./taskresult'); -var logger = require('./../../Common/sources/logger'); var utils = require('./../../Common/sources/utils'); var constants = require('./../../Common/sources/constants'); var commonDefines = require('./../../Common/sources/commondefines'); var docsCoServer = require('./DocsCoServer'); var canvasService = require('./canvasservice'); +var wopiClient = require('./wopiClient'); var storage = require('./../../Common/sources/storage-base'); var formatChecker = require('./../../Common/sources/formatchecker'); var statsDClient = require('./../../Common/sources/statsdclient'); +var storageBase = require('./../../Common/sources/storage-base'); +var operationContext = require('./../../Common/sources/operationContext'); +const sqlBase = require('./databaseConnectors/baseConnector'); +const utilsDocService = require("./utilsDocService"); -var cfgHealthCheckFilePath = config.get('services.CoAuthoring.server.healthcheckfilepath'); -var cfgTokenEnableRequestInbox = config.get('services.CoAuthoring.token.enable.request.inbox'); +const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); var CONVERT_ASYNC_DELAY = 1000; var clientStatsD = statsDClient.getClient(); -function* getConvertStatus(cmd, selectRes, baseUrl, fileTo) { - var status = {url: undefined, err: constants.NO_ERROR}; +function* getConvertStatus(ctx, docId, encryptedUserPassword, selectRes, opt_checkPassword) { + var status = new commonDefines.ConvertStatus(constants.NO_ERROR); if (selectRes.length > 0) { - var docId = cmd.getDocId(); var row = selectRes[0]; + let password = opt_checkPassword && sqlBase.DocumentPassword.prototype.getCurPassword(ctx, row.password); switch (row.status) { - case taskResult.FileStatus.Ok: - status.url = yield storage.getSignedUrl(baseUrl, docId + '/' + fileTo); - break; - case taskResult.FileStatus.Err: - case taskResult.FileStatus.ErrToReload: - status.err = row.status_info; - if (taskResult.FileStatus.ErrToReload == row.status) { - yield canvasService.cleanupCache(docId); + case commonDefines.FileStatus.Ok: + if (password) { + let isCorrectPassword; + if (encryptedUserPassword) { + let decryptedPassword = yield utils.decryptPassword(ctx, password); + let userPassword = yield utils.decryptPassword(ctx, encryptedUserPassword); + isCorrectPassword = decryptedPassword === userPassword; + } + if (isCorrectPassword) { + ctx.logger.debug("getConvertStatus password match"); + status.end = true; + } else { + ctx.logger.debug("getConvertStatus password mismatch"); + status.err = constants.CONVERT_PASSWORD; + } + } else { + status.end = true; } break; - case taskResult.FileStatus.NeedParams: - case taskResult.FileStatus.SaveVersion: - case taskResult.FileStatus.UpdateVersion: - status.err = constants.UNKNOWN; + case commonDefines.FileStatus.Err: + status.err = row.status_info; break; - case taskResult.FileStatus.NeedPassword: + case commonDefines.FileStatus.ErrToReload: + case commonDefines.FileStatus.NeedPassword: status.err = row.status_info; + yield canvasService.cleanupErrToReload(ctx, docId); + break; + case commonDefines.FileStatus.NeedParams: + case commonDefines.FileStatus.SaveVersion: + case commonDefines.FileStatus.UpdateVersion: + status.err = constants.UNKNOWN; break; } var lastOpenDate = row.last_open_date; - if (new Date().getTime() - lastOpenDate.getTime() > utils.CONVERTION_TIMEOUT) { + if (new Date().getTime() - lastOpenDate.getTime() > utils.getConvertionTimeout(ctx)) { status.err = constants.CONVERT_TIMEOUT; } + } else { + status.err = constants.UNKNOWN; } return status; } - -function* convertByCmd(cmd, async, baseUrl, fileTo, opt_healthcheck, opt_priority, opt_expiration, opt_queue) { +function* getConvertPath(ctx, docId, fileTo, formatTo) { + if (constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML === formatTo || constants.AVS_OFFICESTUDIO_FILE_OTHER_ODF === formatTo) { + let list = yield storage.listObjects(ctx, docId); + let baseName = path.basename(fileTo, path.extname(fileTo)); + for (let i = 0; i < list.length; ++i) { + if (path.basename(list[i], path.extname(list[i])) === baseName) { + return list[i]; + } + } + } + return docId + '/' + fileTo; +} +function* getConvertUrl(ctx, baseUrl, fileToPath, title) { + if (title) { + title = path.basename(title, path.extname(title)) + path.extname(fileToPath); + } + return yield storage.getSignedUrl(ctx, baseUrl, fileToPath, commonDefines.c_oAscUrlTypes.Temporary, title); +} +function* convertByCmd(ctx, cmd, async, opt_fileTo, opt_taskExist, opt_priority, opt_expiration, opt_queue, opt_checkPassword) { var docId = cmd.getDocId(); var startDate = null; if (clientStatsD) { startDate = new Date(); } - logger.debug('Start convert request docId = %s', docId); + ctx.logger.debug('Start convert request'); - var task = new taskResult.TaskResultData(); - task.key = docId; - task.status = taskResult.FileStatus.WaitQueue; - task.statusInfo = constants.NO_ERROR; + let bCreate = false; + if (!opt_taskExist) { + let task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; + task.key = docId; + task.status = commonDefines.FileStatus.WaitQueue; + task.statusInfo = constants.NO_ERROR; - var upsertRes = yield taskResult.upsert(task); - //if CLIENT_FOUND_ROWS don't specify 1 row is inserted , 2 row is updated, and 0 row is set to its current values - //http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html - var bCreate = upsertRes.affectedRows == 1; + const upsertRes = yield taskResult.upsert(ctx, task); + bCreate = upsertRes.isInsert; + } var selectRes; var status; - if (!bCreate && !opt_healthcheck) { - selectRes = yield taskResult.select(docId); - status = yield* getConvertStatus(cmd, selectRes, baseUrl, fileTo); - } else { + if (!bCreate) { + selectRes = yield taskResult.select(ctx, docId); + status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword); + } + if (bCreate || (commonDefines.FileStatus.None === selectRes?.[0]?.status)) { var queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); queueData.setCmd(cmd); - queueData.setToFile(fileTo); - if (opt_healthcheck) { - queueData.setFromOrigin(true); + if (opt_fileTo) { + queueData.setToFile(opt_fileTo); } - var priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW + queueData.setFromOrigin(true); + var priority = null != opt_priority ? opt_priority : constants.QUEUE_PRIORITY_LOW; yield* docsCoServer.addTask(queueData, priority, opt_queue, opt_expiration); - status = {url: undefined, err: constants.NO_ERROR}; + status = new commonDefines.ConvertStatus(constants.NO_ERROR); } //wait if (!async) { var waitTime = 0; while (true) { - if (status.url || constants.NO_ERROR != status.err) { + if (status.end || constants.NO_ERROR != status.err) { break; } yield utils.sleep(CONVERT_ASYNC_DELAY); - selectRes = yield taskResult.select(docId); - status = yield* getConvertStatus(cmd, selectRes, baseUrl, fileTo); + selectRes = yield taskResult.select(ctx, docId); + status = yield* getConvertStatus(ctx, cmd.getDocId() ,cmd.getPassword(), selectRes, opt_checkPassword); waitTime += CONVERT_ASYNC_DELAY; - if (waitTime > utils.CONVERTION_TIMEOUT) { + if (waitTime > utils.getConvertionTimeout(ctx)) { status.err = constants.CONVERT_TIMEOUT; } } } - logger.debug('End convert request url %s status %s docId = %s', status.url, status.err, docId); + ctx.logger.debug('End convert request end %s status %s', status.end, status.err); if (clientStatsD) { clientStatsD.timing('coauth.convertservice', new Date() - startDate); } return status; } -function* convertFromChanges(docId, baseUrl, forceSave, opt_userdata, opt_userConnectionId, opt_priority, - opt_expiration, opt_queue, opt_redisKey) { +async function convertFromChanges(ctx, docId, baseUrl, forceSave, externalChangeInfo, opt_userdata, opt_formdata, + opt_userConnectionId, opt_userConnectionDocId, opt_responseKey, opt_priority, + opt_expiration, opt_queue, opt_redisKey, opt_initShardKey, opt_jsonParams) { var cmd = new commonDefines.InputCommand(); cmd.setCommand('sfcm'); cmd.setDocId(docId); - cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_OTHER_TEAMLAB_INNER); + cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML); cmd.setEmbeddedFonts(false); cmd.setCodepage(commonDefines.c_oAscCodePageUtf8); cmd.setDelimiter(commonDefines.c_oAscCsvDelimiter.Comma); cmd.setForceSave(forceSave); + cmd.setExternalChangeInfo(externalChangeInfo); + if (externalChangeInfo.lang) { + //todo lang and region are different + cmd.setLCID(utilsDocService.localeToLCID(externalChangeInfo.lang)); + } if (opt_userdata) { cmd.setUserData(opt_userdata); } + if (opt_formdata) { + //todo put file to storage + cmd.setFormData(opt_formdata); + } if (opt_userConnectionId) { cmd.setUserConnectionId(opt_userConnectionId); } + if (opt_userConnectionDocId) { + cmd.setUserConnectionDocId(opt_userConnectionDocId); + } + if (opt_responseKey) { + cmd.setResponseKey(opt_responseKey); + } if (opt_redisKey) { cmd.setRedisKey(opt_redisKey); } + if (opt_jsonParams) { + cmd.appendJsonParams(opt_jsonParams); + } - yield* canvasService.commandSfctByCmd(cmd, opt_priority, opt_expiration, opt_queue); - return yield* convertByCmd(cmd, true, baseUrl, constants.OUTPUT_NAME, undefined, opt_priority, opt_expiration, opt_queue); + let commandSfctByCmdRes = await canvasService.commandSfctByCmd(ctx, cmd, opt_priority, opt_expiration, opt_queue, opt_initShardKey); + if (!commandSfctByCmdRes) { + return new commonDefines.ConvertStatus(constants.UNKNOWN); + } + var fileTo = constants.OUTPUT_NAME; + let outputExt = formatChecker.getStringFromFormat(cmd.getOutputFormat()); + if (outputExt) { + fileTo += '.' + outputExt; + } + let status = await co(convertByCmd(ctx, cmd, true, fileTo, undefined, opt_priority, opt_expiration, opt_queue)); + if (status.end) { + let fileToPath = await co(getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat())); + status.setExtName(path.extname(fileToPath)); + status.setUrl(await co(getConvertUrl(ctx, baseUrl, fileToPath, cmd.getTitle()))); + } + return status; } function parseIntParam(val){ return (typeof val === 'string') ? parseInt(val) : val; } -function convertRequest(req, res) { +function convertRequest(req, res, isJson) { return co(function* () { - var docId = 'convertRequest'; + let ctx = new operationContext.Context(); try { - var params; - if (req.body && Buffer.isBuffer(req.body)) { - params = JSON.parse(req.body.toString('utf8')); + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('convertRequest start'); + let params; + let authRes = yield docsCoServer.getRequestParams(ctx, req); + if(authRes.code === constants.NO_ERROR){ + params = authRes.params; } else { - params = req.query; - } - if (cfgTokenEnableRequestInbox) { - var authError = constants.VKEY; - var checkJwtRes = docsCoServer.checkJwtHeader(docId, req); - if (checkJwtRes) { - if (checkJwtRes.decoded) { - if (!utils.isEmptyObject(checkJwtRes.decoded.payload)) { - Object.assign(params, checkJwtRes.decoded.payload); - authError = constants.NO_ERROR; - } else if (checkJwtRes.decoded.payloadhash) { - if (docsCoServer.checkJwtPayloadHash(docId, checkJwtRes.decoded.payloadhash, req.body, checkJwtRes.token)) { - authError = constants.NO_ERROR; - } - } else if (!utils.isEmptyObject(checkJwtRes.decoded.query)) { - Object.assign(params, checkJwtRes.decoded.query); - authError = constants.NO_ERROR; - } - } else { - if (constants.JWT_EXPIRED_CODE == checkJwtRes.code) { - authError = constants.VKEY_KEY_EXPIRE; - } - } + ctx.logger.warn('convertRequest auth failed %j', authRes); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(authRes.code), isJson); + return; + } + let filetype = params.filetype || params.fileType || ''; + let outputtype = params.outputtype || params.outputType || ''; + ctx.setDocId(params.key); + + if (params.key && !constants.DOC_ID_REGEX.test(params.key)) { + ctx.logger.warn('convertRequest unexpected key = %s', params.key); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); + return; + } + if (filetype && !constants.EXTENTION_REGEX.test(filetype)) { + ctx.logger.warn('convertRequest unexpected filetype = %s', filetype); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); + return; + } + let outputFormat = formatChecker.getFormatFromString(outputtype); + if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) { + ctx.logger.warn('convertRequest unexpected outputtype = %s', outputtype); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); + return; + } + if (params.pdf) { + if (true === params.pdf.pdfa && constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === outputFormat) { + outputFormat = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA; + } else if (false === params.pdf.pdfa && constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === outputFormat) { + outputFormat = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF; } - if (authError !== constants.NO_ERROR) { - utils.fillResponse(req, res, undefined, authError); - return; + if (params.pdf.form && (constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === outputFormat || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === outputFormat)) { + outputFormat = constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF; } } - + //todo use hash of params as id + let docId = 'conv_' + params.key + '_' + outputFormat; var cmd = new commonDefines.InputCommand(); cmd.setCommand('conv'); cmd.setUrl(params.url); cmd.setEmbeddedFonts(false);//params.embeddedfonts']; - cmd.setFormat(params.filetype); - var outputtype = params.outputtype || ''; - docId = 'conv_' + params.key + '_' + outputtype; + cmd.setFormat(filetype); cmd.setDocId(docId); - var fileTo = constants.OUTPUT_NAME + '.' + outputtype; - cmd.setOutputFormat(formatChecker.getFormatFromString(outputtype)); + cmd.setOutputFormat(outputFormat); + let outputExt = formatChecker.getStringFromFormat(cmd.getOutputFormat()); + cmd.setCodepage(commonDefines.c_oAscEncodingsMap[params.codePage] || commonDefines.c_oAscCodePageUtf8); cmd.setDelimiter(parseIntParam(params.delimiter) || commonDefines.c_oAscCsvDelimiter.Comma); - cmd.setDoctParams(parseIntParam(params.doctparams)); - cmd.setPassword(params.password); + if(undefined != params.delimiterChar) + cmd.setDelimiterChar(params.delimiterChar); + if (params.region) { + cmd.setLCID(utilsDocService.localeToLCID(params.region)); + } + let jsonParams = {}; + if (params.documentLayout) { + jsonParams['documentLayout'] = params.documentLayout; + } + if (params.spreadsheetLayout) { + jsonParams['spreadsheetLayout'] = params.spreadsheetLayout; + } + if (params.watermark) { + jsonParams['watermark'] = params.watermark; + } + if (Object.keys(jsonParams).length > 0) { + cmd.appendJsonParams(jsonParams); + } + if (params.password) { + if (params.password.length > constants.PASSWORD_MAX_LENGTH) { + ctx.logger.warn('convertRequest password too long actual = %s; max = %s', params.password.length, constants.PASSWORD_MAX_LENGTH); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); + return; + } + let encryptedPassword = yield utils.encryptPassword(ctx, params.password); + cmd.setPassword(encryptedPassword); + } + if (authRes.isDecoded) { + cmd.setWithAuthorization(true); + } var thumbnail = params.thumbnail; if (thumbnail) { if (typeof thumbnail === 'string') { @@ -242,28 +355,372 @@ function convertRequest(req, res) { break; } cmd.setThumbnail(thumbnailData); - cmd.setOutputFormat(constants.AVS_OFFICESTUDIO_FILE_IMAGE); - if (false == thumbnailData.getFirst()) { - cmd.setTitle(constants.OUTPUT_NAME + '.zip'); + if (false === thumbnailData.getFirst() && 0 !== (constants.AVS_OFFICESTUDIO_FILE_IMAGE & cmd.getOutputFormat())) { + outputExt = 'zip'; + } + } + var documentRenderer = params.documentRenderer; + if (documentRenderer) { + if (typeof documentRenderer === 'string') { + documentRenderer = JSON.parse(documentRenderer); + } + var textParamsData = new commonDefines.CTextParams(); + switch (documentRenderer.textAssociation) { + case 'plainParagraph': + textParamsData.setAssociation(3); + break; + case 'plainLine': + textParamsData.setAssociation(2); + break; + case 'blockLine': + textParamsData.setAssociation(1); + break; + case 'blockChar': + default: + textParamsData.setAssociation(0); + break; } + cmd.setTextParams(textParamsData); + } + if (params.title) { + cmd.setTitle(path.basename(params.title, path.extname(params.title)) + '.' + outputExt); } var async = (typeof params.async === 'string') ? 'true' == params.async : params.async; - + if (async && !req.query[constants.SHARD_KEY_API_NAME] && !req.query[constants.SHARD_KEY_WOPI_NAME] && process.env.DEFAULT_SHARD_KEY) { + ctx.logger.warn('convertRequest set async=false. Pass query string parameter "%s" to correctly process request in sharded cluster', constants.SHARD_KEY_API_NAME); + async = false; + } if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN !== cmd.getOutputFormat()) { - var status = yield* convertByCmd(cmd, async, utils.getBaseUrlByRequest(req), fileTo); - utils.fillResponse(req, res, status.url, status.err); + let fileTo = constants.OUTPUT_NAME + '.' + outputExt; + var status = yield* convertByCmd(ctx, cmd, async, fileTo, undefined, undefined, undefined, undefined, true); + if (status.end) { + let fileToPath = yield* getConvertPath(ctx, docId, fileTo, cmd.getOutputFormat()); + status.setExtName(path.extname(fileToPath)); + status.setUrl(yield* getConvertUrl(ctx, utils.getBaseUrlByRequest(ctx, req), fileToPath, cmd.getTitle())); + ctx.logger.debug('convertRequest: url = %s', status.url); + } + utils.fillResponse(req, res, status, isJson); } else { var addresses = utils.forwarded(req); - logger.error('Error convert unknown outputtype: query = %j from = %s docId = %s', params, addresses, docId); - utils.fillResponse(req, res, undefined, constants.UNKNOWN); + ctx.logger.warn('Error convert unknown outputtype: query = %j from = %s', params, addresses); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson); } + } catch (e) { + ctx.logger.error('convertRequest error: %s', e.stack); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson); + } finally { + ctx.logger.info('convertRequest end'); + } + }); +} +function convertRequestJson(req, res) { + return convertRequest(req, res, true); +} +function convertRequestXml(req, res) { + return convertRequest(req, res, false); +} + +function builderRequest(req, res) { + return co(function* () { + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('builderRequest start'); + let authRes = yield docsCoServer.getRequestParams(ctx, req); + let params = authRes.params; + let docId = params.key; + ctx.setDocId(docId); + + let error = authRes.code; + let urls; + let end = false; + let needCreateId = !docId; + let isInBody = req.body && Buffer.isBuffer(req.body) && req.body.length > 0; + if (error === constants.NO_ERROR && (params.key || params.url || isInBody)) { + if (needCreateId) { + let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'bld_', 8); + docId = task.key; + ctx.setDocId(docId); + } + let cmd = new commonDefines.InputCommand(); + cmd.setCommand('builder'); + cmd.setBuilderParams({argument: params.argument}); + if (authRes.isDecoded) { + cmd.setWithAuthorization(true); + } + cmd.setDocId(docId); + if (params.url) { + cmd.setUrl(params.url); + cmd.setFormat('docbuilder'); + } else if (isInBody) { + yield storageBase.putObject(ctx, docId + '/script.docbuilder', req.body, req.body.length); + } + if (needCreateId) { + let queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); + queueData.setCmd(cmd); + yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); + } + let async = (typeof params.async === 'string') ? 'true' === params.async : params.async; + if (async && !req.query[constants.SHARD_KEY_API_NAME] && !req.query[constants.SHARD_KEY_WOPI_NAME] && process.env.DEFAULT_SHARD_KEY) { + ctx.logger.warn('builderRequest set async=false. Pass query string parameter "%s" to correctly process request in sharded cluster', constants.SHARD_KEY_API_NAME); + async = false; + } + let status = yield* convertByCmd(ctx, cmd, async, undefined, undefined, constants.QUEUE_PRIORITY_LOW); + end = status.end; + error = status.err; + if (end) { + urls = yield storageBase.getSignedUrls(ctx, utils.getBaseUrlByRequest(ctx, req), docId + '/output', + commonDefines.c_oAscUrlTypes.Temporary); + } + } else if (error === constants.NO_ERROR) { + error = constants.UNKNOWN; + } + ctx.logger.debug('End builderRequest request: urls = %j end = %s error = %s', urls, end, error); + utils.fillResponseBuilder(res, docId, urls, end, error); } catch (e) { - logger.error('Error convert: docId = %s\r\n%s', docId, e.stack); - utils.fillResponse(req, res, undefined, constants.UNKNOWN); + ctx.logger.error('Error builderRequest: %s', e.stack); + utils.fillResponseBuilder(res, undefined, undefined, undefined, constants.UNKNOWN); + } finally { + ctx.logger.info('builderRequest end'); + } + }); +} +function convertTo(req, res) { + return co(function*() { + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('convert-to start'); + let format = req.body['format']; + if (req.params.format) { + format = req.params.format; + } + //todo https://github.com/LibreOffice/core/blob/9d3366f5b392418dc83bc0adbe3d215cff4b3605/desktop/source/lib/init.cxx#L3478 + let password = req.body['Password']; + if (password) { + if (password.length > constants.PASSWORD_MAX_LENGTH) { + ctx.logger.warn('convert-to Password too long actual = %s; max = %s', password.length, constants.PASSWORD_MAX_LENGTH); + res.sendStatus(400); + return; + } + } + //by analogy with Password + let passwordToOpen = req.body['PasswordToOpen']; + if (passwordToOpen) { + if (passwordToOpen.length > constants.PASSWORD_MAX_LENGTH) { + ctx.logger.warn('convert-to PasswordToOpen too long actual = %s; max = %s', passwordToOpen.length, constants.PASSWORD_MAX_LENGTH); + res.sendStatus(400); + return; + } + } + let pdfVer = req.body['PDFVer']; + if (pdfVer && pdfVer.startsWith("PDF/A") && 'pdf' === format) { + format = 'pdfa'; + } + let fullSheetPreview = req.body['FullSheetPreview']; + let lang = req.body['lang']; + let outputFormat = formatChecker.getFormatFromString(format); + if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) { + ctx.logger.warn('convert-to unexpected format = %s', format); + res.sendStatus(400); + return; + } + let docId, fileTo, status, originalname; + if (req.files?.length > 0 && req.files[0].originalname && req.files[0].buffer) { + const file = req.files[0]; + originalname = file.originalname; + let filetype = path.extname(file.originalname).substring(1); + if (filetype && !constants.EXTENTION_REGEX.test(filetype)) { + ctx.logger.warn('convertRequest unexpected filetype = %s', filetype); + res.sendStatus(400); + return; + } + + let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8); + docId = task.key; + ctx.setDocId(docId); + + //todo stream + let buffer = file.buffer; + yield storageBase.putObject(ctx, docId + '/origin.' + filetype, buffer, buffer.length); + + let cmd = new commonDefines.InputCommand(); + cmd.setCommand('conv'); + cmd.setDocId(docId); + cmd.setFormat(filetype); + cmd.setOutputFormat(outputFormat); + cmd.setCodepage(commonDefines.c_oAscCodePageUtf8); + cmd.setDelimiter(commonDefines.c_oAscCsvDelimiter.Comma); + if (lang) { + cmd.setLCID(utilsDocService.localeToLCID(lang)); + } + if (fullSheetPreview) { + cmd.appendJsonParams({'spreadsheetLayout': { + "ignorePrintArea": true, + "fitToWidth": 1, + "fitToHeight": 1 + }}); + } else { + cmd.appendJsonParams({'spreadsheetLayout': { + "ignorePrintArea": true, + "fitToWidth": 0, + "fitToHeight": 0, + "scale": 100 + }}); + } + if (password) { + let encryptedPassword = yield utils.encryptPassword(ctx, password); + cmd.setSavePassword(encryptedPassword); + } + if (passwordToOpen) { + let encryptedPassword = yield utils.encryptPassword(ctx, passwordToOpen); + cmd.setPassword(encryptedPassword); + } + + fileTo = constants.OUTPUT_NAME; + let outputExt = formatChecker.getStringFromFormat(outputFormat); + if (outputExt) { + fileTo += '.' + outputExt; + } + + let queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); + queueData.setCmd(cmd); + queueData.setToFile(fileTo); + queueData.setFromOrigin(true); + yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); + + let async = false; + status = yield* convertByCmd(ctx, cmd, async, fileTo); + } + if (status && status.end && constants.NO_ERROR === status.err) { + let filename = path.basename(originalname, path.extname(originalname)) + path.extname(fileTo); + let streamObj = yield storage.createReadStream(ctx, `${docId}/${fileTo}`); + res.setHeader('Content-Disposition', utils.getContentDisposition(filename, null, constants.CONTENT_DISPOSITION_INLINE)); + res.setHeader('Content-Length', streamObj.contentLength); + res.setHeader('Content-Type', mime.getType(filename)); + yield utils.pipeStreams(streamObj.readStream, res, true); + } else { + ctx.logger.error('convert-to error status:%j', status); + res.sendStatus(400); + } + } catch (err) { + ctx.logger.error('convert-to error:%s', err.stack); + res.sendStatus(400); + } finally { + ctx.logger.info('convert-to end'); + } + }); +} +function convertAndEdit(ctx, wopiParams, filetypeFrom, filetypeTo) { + return co(function*() { + try { + ctx.logger.info('convert-and-edit start'); + + let task = yield* taskResult.addRandomKeyTask(ctx, undefined, 'conv_', 8); + let docId = task.key; + let outputFormat = formatChecker.getFormatFromString(filetypeTo); + if (constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === outputFormat) { + ctx.logger.debug('convert-and-edit unknown outputFormat %s', filetypeTo); + return; + } + + let cmd = new commonDefines.InputCommand(); + cmd.setCommand('conv'); + cmd.setDocId(docId); + cmd.setUrl('dummy-url'); + cmd.setWopiParams(wopiParams); + cmd.setFormat(filetypeFrom); + cmd.setOutputFormat(outputFormat); + + let fileTo = constants.OUTPUT_NAME; + let outputExt = formatChecker.getStringFromFormat(outputFormat); + if (outputExt) { + fileTo += '.' + outputExt; + } + + let queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); + queueData.setCmd(cmd); + queueData.setToFile(fileTo); + yield* docsCoServer.addTask(queueData, constants.QUEUE_PRIORITY_LOW); + + let async = true; + yield* convertByCmd(ctx, cmd, async, fileTo); + return docId; + } catch (err) { + ctx.logger.error('convert-and-edit error:%s', err.stack); + } finally { + ctx.logger.info('convert-and-edit end'); } }); } +function getConverterHtmlHandler(req, res) { + return co(function*() { + let isJson = true; + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('convert-and-edit-handler start'); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + + let wopiSrc = req.query['wopisrc']; + let access_token = req.query['access_token']; + let targetext = req.query['targetext']; + let docId = req.query['docid']; + ctx.setDocId(docId); + if (!(wopiSrc && access_token && access_token && targetext && docId) || + constants.AVS_OFFICESTUDIO_FILE_UNKNOWN === formatChecker.getFormatFromString(targetext)) { + ctx.logger.debug('convert-and-edit-handler invalid params: WOPISrc=%s; access_token=%s; targetext=%s; docId=%s', wopiSrc, access_token, targetext, docId); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.CONVERT_PARAMS), isJson); + return; + } + let token = req.query['token']; + if (tenTokenEnableBrowser) { + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Browser); + if (checkJwtRes.decoded) { + docId = checkJwtRes.decoded.docId; + } else { + ctx.logger.debug('convert-and-edit-handler invalid token %j', token); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.VKEY), isJson); + return; + } + } + ctx.setDocId(docId); + let selectRes = yield taskResult.select(ctx, docId); + let status = yield* getConvertStatus(ctx, docId, undefined, selectRes); + if (status.end && constants.NO_ERROR === status.err) { + let fileTo = `${docId}/${constants.OUTPUT_NAME}.${targetext}`; + + let metadata = yield storage.headObject(ctx, fileTo); + let streamObj = yield storage.createReadStream(ctx, fileTo); + let putRelativeRes = yield wopiClient.putRelativeFile(ctx, wopiSrc, access_token, null, streamObj.readStream, metadata.ContentLength, `.${targetext}`, undefined, true); + if (putRelativeRes) { + status.setUrl(putRelativeRes.HostEditUrl); + status.setExtName('.' + targetext); + } else { + status.err = constants.UNKNOWN; + } + } + utils.fillResponse(req, res, status, isJson); + } catch (err) { + ctx.logger.error('convert-and-edit-handler error:%s', err.stack); + utils.fillResponse(req, res, new commonDefines.ConvertStatus(constants.UNKNOWN), isJson); + } finally { + ctx.logger.info('convert-and-edit-handler end'); + } + }); +} exports.convertFromChanges = convertFromChanges; -exports.convert = convertRequest; +exports.convertJson = convertRequestJson; +exports.convertXml = convertRequestXml; +exports.convertTo = convertTo; +exports.convertAndEdit = convertAndEdit; +exports.getConverterHtmlHandler = getConverterHtmlHandler; +exports.builder = builderRequest; diff --git a/DocService/sources/database.js b/DocService/sources/database.js index 5dcf998662..71b108ec16 100644 --- a/DocService/sources/database.js +++ b/DocService/sources/database.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -47,11 +47,10 @@ exports.insert = function (_collectionName, _newElement) { logger.error ("Error _db"); return; } - - // Открываем базу данных + _db.open (function (err, db) { if (!err) { - // Открываем коллекцию. ЕÑли её не ÑущеÑтвует, она будет Ñоздана + // open collection. If it doesn't exist, it will be created db.collection(_collectionName, function(err, collection) { if (!err) { collection.insert (_newElement); @@ -74,14 +73,13 @@ exports.remove = function (_collectionName, _removeElements) { return; } - // Открываем базу данных + // Opening the database _db.open (function (err, db) { if (!err) { - // Открываем коллекцию. ЕÑли её не ÑущеÑтвует, она будет Ñоздана + // open collection. If it doesn't exist, it will be created db.collection(_collectionName, function(err, collection) { if (!err) { - collection.remove (_removeElements, function(err, collection) { - // Ð’Ñе Ñлементы удалены + collection.remove (_removeElements, function(err, collection) { logger.info ("All elements remove"); }); } else { @@ -105,14 +103,14 @@ exports.load = function (_collectionName, callbackFunction) { var result = []; - // Открываем базу данных + // opening database _db.open (function (err, db) { - // Открываем коллекцию. ЕÑли её не ÑущеÑтвует, она будет Ñоздана + // open collection. If it doesn't exist, it will be created db.collection(_collectionName, function(err, collection) { - // Получаем вÑе Ñлементы коллекции Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ find() + // Get all elements of a collection with find() collection.find(function(err, cursor) { cursor.each(function(err, item) { - // Null обозначает поÑледний Ñлемент + // Null denotes the last element if (item != null) { if (!result.hasOwnProperty (item.docid)) result[item.docid] = [item]; diff --git a/DocService/sources/databaseConnectors/baseConnector.js b/DocService/sources/databaseConnectors/baseConnector.js new file mode 100644 index 0000000000..1668a23015 --- /dev/null +++ b/DocService/sources/databaseConnectors/baseConnector.js @@ -0,0 +1,402 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const sqlDataBaseType = { + mySql : 'mysql', + mariaDB : 'mariadb', + msSql : 'mssql', + postgreSql : 'postgres', + dameng : 'dameng', + oracle : 'oracle' +}; + +const connectorUtilities = require('./connectorUtilities'); +const utils = require('../../../Common/sources/utils'); +const bottleneck = require('bottleneck'); +const config = require('config'); + +const configSql = config.get('services.CoAuthoring.sql'); +const cfgTableResult = configSql.get('tableResult'); +const cfgTableChanges = configSql.get('tableChanges'); +const maxPacketSize = configSql.get('max_allowed_packet'); // The default size for a query to the database is 1Mb - 1 (because it does not write 1048575, but writes 1048574) +const cfgBottleneckGetChanges = config.get('bottleneck.getChanges'); +const dbType = configSql.get('type'); + +const reservoirMaximum = cfgBottleneckGetChanges.reservoirIncreaseMaximum || cfgBottleneckGetChanges.reservoirRefreshAmount; +const group = new bottleneck.Group(cfgBottleneckGetChanges); +const g_oCriticalSection = {}; + +let dbInstance; +switch (dbType) { + case sqlDataBaseType.mySql: + case sqlDataBaseType.mariaDB: + dbInstance = require('./mysqlConnector'); + break; + case sqlDataBaseType.msSql: + dbInstance = require('./mssqlConnector'); + break; + case sqlDataBaseType.dameng: + dbInstance = require('./damengConnector'); + break; + case sqlDataBaseType.oracle: + dbInstance = require('./oracleConnector'); + break; + default: + dbInstance = require('./postgreConnector'); + break; +} + +let isSupportFastInsert = !!dbInstance.insertChanges; +const addSqlParameter = dbInstance.addSqlParameter; + +function getChangesSize(changes) { + return changes.reduce((accumulator, currentValue) => accumulator + currentValue.change_data.length, 0); +} + +function insertChangesPromiseCompatibility(ctx, objChanges, docId, index, user) { + return new Promise(function(resolve, reject) { + _insertChangesCallback(ctx, 0, objChanges, docId, index, user, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +function insertChangesPromiseFast(ctx, objChanges, docId, index, user) { + return new Promise(function(resolve, reject) { + dbInstance.insertChanges(ctx, cfgTableChanges, 0, objChanges, docId, index, user, function(error, result, isSupported) { + isSupportFastInsert = isSupported; + if (error) { + if (!isSupportFastInsert) { + resolve(insertChangesPromiseCompatibility(ctx, objChanges, docId, index, user)); + } else { + reject(error); + } + } else { + resolve(result); + } + }); + }); +} + +function insertChangesPromise(ctx, objChanges, docId, index, user) { + if (isSupportFastInsert) { + return insertChangesPromiseFast(ctx, objChanges, docId, index, user); + } else { + return insertChangesPromiseCompatibility(ctx, objChanges, docId, index, user); + } +} + +function _getDateTime2(oDate) { + return oDate.toISOString().slice(0, 19).replace('T', ' '); +} + +function _insertChangesCallback(ctx, startIndex, objChanges, docId, index, user, callback) { + var sqlCommand = `INSERT INTO ${cfgTableChanges} VALUES`; + var i = startIndex, l = objChanges.length, lengthUtf8Current = sqlCommand.length, lengthUtf8Row = 0, values = []; + if (i === l) + return; + + const indexBytes = 4; + const timeBytes = 8; + for (; i < l; ++i, ++index) { + //49 - length of "($1001,... $1008)," + //4 is max utf8 bytes per symbol + lengthUtf8Row = 49 + 4 * (ctx.tenant.length + docId.length + user.id.length + user.idOriginal.length + user.username.length + objChanges[i].change.length) + indexBytes + timeBytes; + if (lengthUtf8Row + lengthUtf8Current >= maxPacketSize && i > startIndex) { + sqlCommand += ';'; + (function(tmpStart, tmpIndex) { + dbInstance.sqlQuery(ctx, sqlCommand, function() { + // do not remove lock, but we continue to add + _insertChangesCallback(ctx, tmpStart, objChanges, docId, tmpIndex, user, callback); + }, undefined, undefined, values); + })(i, index); + return; + } + let p0 = addSqlParameter(ctx.tenant, values); + let p1 = addSqlParameter(docId, values); + let p2 = addSqlParameter(index, values); + let p3 = addSqlParameter(user.id, values); + let p4 = addSqlParameter(user.idOriginal, values); + let p5 = addSqlParameter(user.username, values); + let p6 = addSqlParameter(objChanges[i].change, values); + let p7 = addSqlParameter(objChanges[i].time, values); + if (i > startIndex) { + sqlCommand += ','; + } + sqlCommand += `(${p0},${p1},${p2},${p3},${p4},${p5},${p6},${p7})`; + lengthUtf8Current += lengthUtf8Row; + } + + sqlCommand += ';'; + dbInstance.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values); +} + +function deleteChangesCallback(ctx, docId, deleteIndex, callback) { + let sqlCommand, values = []; + let p1 = addSqlParameter(ctx.tenant, values); + let p2 = addSqlParameter(docId, values); + if (null !== deleteIndex) { + let sqlParam2 = addSqlParameter(deleteIndex, values); + sqlCommand = `DELETE FROM ${cfgTableChanges} WHERE tenant=${p1} AND id=${p2} AND change_id >= ${sqlParam2};`; + } else { + sqlCommand = `DELETE FROM ${cfgTableChanges} WHERE tenant=${p1} AND id=${p2};`; + } + dbInstance.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values); +} + +function deleteChangesPromise(ctx, docId, deleteIndex) { + return new Promise(function(resolve, reject) { + deleteChangesCallback(ctx, docId, deleteIndex, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +function deleteChanges(ctx, docId, deleteIndex) { + lockCriticalSection(docId, function () {_deleteChanges(ctx, docId, deleteIndex);}); +} + +function _deleteChanges (ctx, docId, deleteIndex) { + deleteChangesCallback(ctx, docId, deleteIndex, function () {unLockCriticalSection(docId);}); +} + +function getChangesIndex(ctx, docId, callback) { + let values = []; + let p1 = addSqlParameter(ctx.tenant, values); + let p2 = addSqlParameter(docId, values); + var sqlCommand = `SELECT MAX(change_id) as change_id FROM ${cfgTableChanges} WHERE tenant=${p1} AND id=${p2};`; + dbInstance.sqlQuery(ctx, sqlCommand, callback, undefined, undefined, values); +} + +function getChangesIndexPromise(ctx, docId) { + return new Promise(function(resolve, reject) { + getChangesIndex(ctx, docId, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +function getChangesPromise(ctx, docId, optStartIndex, optEndIndex, opt_time) { + let limiter = group.key(`${ctx.tenant}\t${docId}\tchanges`); + return limiter.schedule(() => { + return new Promise(function(resolve, reject) { + let values = []; + let sqlParam = addSqlParameter(ctx.tenant, values); + let sqlWhere = `tenant=${sqlParam}`; + sqlParam = addSqlParameter(docId, values); + sqlWhere += ` AND id=${sqlParam}`; + if (null != optStartIndex) { + sqlParam = addSqlParameter(optStartIndex, values); + sqlWhere += ` AND change_id>=${sqlParam}`; + } + if (null != optEndIndex) { + sqlParam = addSqlParameter(optEndIndex, values); + sqlWhere += ` AND change_id<${sqlParam}`; + } + if (null != opt_time) { + if (!(opt_time instanceof Date)) { + opt_time = new Date(opt_time); + } + sqlParam = addSqlParameter(opt_time, values); + sqlWhere += ` AND change_date<=${sqlParam}`; + } + sqlWhere += ' ORDER BY change_id ASC'; + var sqlCommand = `SELECT * FROM ${cfgTableChanges} WHERE ${sqlWhere};`; + + dbInstance.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + if (reservoirMaximum > 0) { + let size = Math.min(getChangesSize(result), reservoirMaximum); + let cur = limiter.incrementReservoir(-size).then((cur) => { + ctx.logger.debug("getChangesPromise bottleneck reservoir cur=%s", cur); + resolve(result); + }); + } else { + resolve(result); + } + } + }, undefined, undefined, values); + }); + }); +} + +function getDocumentsWithChanges(ctx) { + return new Promise(function(resolve, reject) { + const sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE EXISTS(SELECT id FROM ${cfgTableChanges} WHERE tenant=${cfgTableResult}.tenant AND id = ${cfgTableResult}.id LIMIT 1);`; + dbInstance.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }, false, false); + }); +} + + +function getExpired(ctx, maxCount, expireSeconds) { + return new Promise(function(resolve, reject) { + const values = []; + const expireDate = new Date(); + utils.addSeconds(expireDate, -expireSeconds); + const date = addSqlParameter(expireDate, values); + const count = addSqlParameter(maxCount, values); + const sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE last_open_date <= ${date}` + + ` AND NOT EXISTS(SELECT tenant, id FROM ${cfgTableChanges} WHERE ${cfgTableChanges}.tenant = ${cfgTableResult}.tenant AND ${cfgTableChanges}.id = ${cfgTableResult}.id LIMIT 1) LIMIT ${count};`; + dbInstance.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }, false, false, values); + }); +} +function getCountWithStatus(ctx, status, expireMs) { + return new Promise(function(resolve, reject) { + const values = []; + const expireDate = new Date(Date.now() - expireMs); + const sqlStatus = addSqlParameter(status, values); + const sqlDate = addSqlParameter(expireDate, values); + const sqlCommand = `SELECT COUNT(id) AS count FROM ${cfgTableResult} WHERE status=${sqlStatus} AND last_open_date>${sqlDate};`; + dbInstance.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + let res = Number(result[0].count) + resolve(!isNaN(res) ? res : 0); + } + }, false, false, values); + }); +} + +function isLockCriticalSection(id) { + return !!(g_oCriticalSection[id]); +} + +// critical section +function lockCriticalSection(id, callback) { + if (g_oCriticalSection[id]) { + // wait + g_oCriticalSection[id].push(callback); + return; + } + // lock + g_oCriticalSection[id] = []; + g_oCriticalSection[id].push(callback); + callback(); +} + +function unLockCriticalSection(id) { + var arrCallbacks = g_oCriticalSection[id]; + arrCallbacks.shift(); + if (0 < arrCallbacks.length) + arrCallbacks[0](); + else + delete g_oCriticalSection[id]; +} + +function healthCheck(ctx) { + return new Promise(function(resolve, reject) { + //SELECT 1; usefull for H2, MySQL, Microsoft SQL Server, PostgreSQL, SQLite + //http://stackoverflow.com/questions/3668506/efficient-sql-test-query-or-validation-query-that-will-work-across-all-or-most + dbInstance.sqlQuery(ctx, 'SELECT 1;', function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +function getEmptyCallbacks(ctx) { + return new Promise(function(resolve, reject) { + const sqlCommand = `SELECT DISTINCT t1.tenant, t1.id FROM ${cfgTableChanges} t1 LEFT JOIN ${cfgTableResult} t2 ON t2.tenant = t1.tenant AND t2.id = t1.id WHERE t2.callback = '';`; + dbInstance.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); +} + +function getTableColumns(ctx, tableName) { + return new Promise(function(resolve, reject) { + let values = []; + let sqlParam = addSqlParameter(tableName, values); + const sqlCommand = `SELECT column_name as "column_name" FROM information_schema.COLUMNS WHERE TABLE_NAME = ${sqlParam};`; + dbInstance.sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + reject(error); + } else { + resolve(result); + } + }, undefined, undefined, values); + }); +} + +module.exports = { + insertChangesPromise, + deleteChangesPromise, + deleteChanges, + getChangesIndexPromise, + getChangesPromise, + isLockCriticalSection, + getDocumentsWithChanges, + getExpired, + getCountWithStatus, + healthCheck, + getEmptyCallbacks, + getTableColumns, + getDateTime: _getDateTime2, + ...connectorUtilities, + ...dbInstance +}; \ No newline at end of file diff --git a/DocService/sources/databaseConnectors/connectorUtilities.js b/DocService/sources/databaseConnectors/connectorUtilities.js new file mode 100644 index 0000000000..3d4a259c2c --- /dev/null +++ b/DocService/sources/databaseConnectors/connectorUtilities.js @@ -0,0 +1,235 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const constants = require('../../../Common/sources/constants'); + +function UserCallback() { + this.userIndex = undefined; + this.callback = undefined; +} +UserCallback.prototype.fromValues = function(userIndex, callback){ + if(null !== userIndex){ + this.userIndex = userIndex; + } + if(null !== callback){ + this.callback = callback; + } +}; +UserCallback.prototype.delimiter = constants.CHAR_DELIMITER; +UserCallback.prototype.toSQLInsert = function(){ + return this.delimiter + JSON.stringify(this); +}; +UserCallback.prototype.getCallbackByUserIndex = function(ctx, callbacksStr, opt_userIndex) { + ctx.logger.debug("getCallbackByUserIndex: userIndex = %s callbacks = %s", opt_userIndex, callbacksStr); + if (!callbacksStr || !callbacksStr.startsWith(UserCallback.prototype.delimiter)) { + let index = callbacksStr.indexOf(UserCallback.prototype.delimiter); + if (-1 === index) { + //old format + return callbacksStr; + } else { + //mix of old and new format + callbacksStr = callbacksStr.substring(index); + } + } + let callbacks = callbacksStr.split(UserCallback.prototype.delimiter); + let callbackUrl = ""; + for (let i = 1; i < callbacks.length; ++i) { + let callback = JSON.parse(callbacks[i]); + callbackUrl = callback.callback; + if (callback.userIndex === opt_userIndex) { + break; + } + } + return callbackUrl; +}; +UserCallback.prototype.getCallbacks = function(ctx, callbacksStr) { + ctx.logger.debug("getCallbacks: callbacks = %s", callbacksStr); + if (!callbacksStr || !callbacksStr.startsWith(UserCallback.prototype.delimiter)) { + let index = callbacksStr.indexOf(UserCallback.prototype.delimiter); + if (-1 === index) { + //old format + return [callbacksStr]; + } else { + //mix of old and new format + callbacksStr = callbacksStr.substring(index); + } + } + let callbacks = callbacksStr.split(UserCallback.prototype.delimiter); + let res = []; + for (let i = 1; i < callbacks.length; ++i) { + let callback = JSON.parse(callbacks[i]); + res.push(callback.callback); + } + return res; +}; + +function DocumentPassword() { + this.password = undefined; + this.change = undefined; +} +DocumentPassword.prototype.fromString = function(passwordStr){ + var parsed = JSON.parse(passwordStr); + this.fromValues(parsed.password, parsed.change); +}; +DocumentPassword.prototype.fromValues = function(password, change){ + if(null !== password){ + this.password = password; + } + if(null !== change) { + this.change = change; + } +}; +DocumentPassword.prototype.delimiter = constants.CHAR_DELIMITER; +DocumentPassword.prototype.toSQLInsert = function(){ + return this.delimiter + JSON.stringify(this); +}; +DocumentPassword.prototype.isInitial = function(){ + return !this.change; +}; +DocumentPassword.prototype.getDocPassword = function(ctx, docPasswordStr) { + let res = {initial: undefined, current: undefined, change: undefined}; + if (docPasswordStr) { + ctx.logger.debug("getDocPassword: passwords = %s", docPasswordStr); + let passwords = docPasswordStr.split(UserCallback.prototype.delimiter); + + for (let i = 1; i < passwords.length; ++i) { + let password = new DocumentPassword(); + password.fromString(passwords[i]); + if (password.isInitial()) { + res.initial = password.password; + } else { + res.change = password.change; + } + res.current = password.password; + } + } + return res; +}; +DocumentPassword.prototype.getCurPassword = function(ctx, docPasswordStr) { + let docPassword = this.getDocPassword(ctx, docPasswordStr); + return docPassword.current; +}; +DocumentPassword.prototype.hasPasswordChanges = function(ctx, docPasswordStr) { + let docPassword = this.getDocPassword(ctx, docPasswordStr); + return docPassword.initial !== docPassword.current; +}; + +function DocumentAdditional() { + this.data = []; +} +DocumentAdditional.prototype.delimiter = constants.CHAR_DELIMITER; +DocumentAdditional.prototype.toSQLInsert = function() { + if (this.data.length) { + let vals = this.data.map((currentValue) => { + return JSON.stringify(currentValue); + }); + return this.delimiter + vals.join(this.delimiter); + } else { + return null; + } +}; +DocumentAdditional.prototype.fromString = function(str) { + if (!str) { + return; + } + let vals = str.split(this.delimiter).slice(1); + this.data = vals.map((currentValue) => { + return JSON.parse(currentValue); + }); +}; +DocumentAdditional.prototype.setOpenedAt = function(time, timezoneOffset, headingsColor) { + let additional = new DocumentAdditional(); + additional.data.push({time, timezoneOffset, headingsColor}); + return additional.toSQLInsert(); +}; +DocumentAdditional.prototype.getOpenedAt = function(str) { + let res; + let val = new DocumentAdditional(); + val.fromString(str); + val.data.forEach((elem) => { + if (undefined !== elem.timezoneOffset) { + res = elem.time - (elem.timezoneOffset * 60 * 1000); + } + }); + return res; +}; +DocumentAdditional.prototype.getDocumentLayout = function(str) { + let res; + let val = new DocumentAdditional(); + val.fromString(str); + val.data.forEach((elem) => { + if (undefined !== elem.timezoneOffset) { + res = {openedAt: elem.time - (elem.timezoneOffset * 60 * 1000), headingsColor: elem.headingsColor}; + } + }); + return res; +} + +DocumentAdditional.prototype.setShardKey = function(shardKey) { + let additional = new DocumentAdditional(); + additional.data.push({shardKey}); + return additional.toSQLInsert(); +}; +DocumentAdditional.prototype.getShardKey = function(str) { + let res; + let val = new DocumentAdditional(); + val.fromString(str); + val.data.forEach((elem) => { + if (elem.shardKey) { + res = elem.shardKey; + } + }); + return res; +}; + +DocumentAdditional.prototype.setWopiSrc = function(wopiSrc) { + let additional = new DocumentAdditional(); + additional.data.push({wopiSrc}); + return additional.toSQLInsert(); +}; +DocumentAdditional.prototype.getWopiSrc = function(str) { + let res; + let val = new DocumentAdditional(); + val.fromString(str); + val.data.forEach((elem) => { + if (elem.wopiSrc) { + res = elem.wopiSrc; + } + }); + return res; +}; + +module.exports = { + UserCallback, + DocumentPassword, + DocumentAdditional +} \ No newline at end of file diff --git a/DocService/sources/databaseConnectors/damengConnector.js b/DocService/sources/databaseConnectors/damengConnector.js new file mode 100644 index 0000000000..84fa47333d --- /dev/null +++ b/DocService/sources/databaseConnectors/damengConnector.js @@ -0,0 +1,225 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const connectorUtilities = require('./connectorUtilities'); +const db = require('dmdb'); +const config = require('config'); + +const configSql = config.get('services.CoAuthoring.sql'); +const cfgDbHost = configSql.get('dbHost'); +const cfgDbPort = configSql.get('dbPort'); +const cfgDbUser = configSql.get('dbUser'); +const cfgDbPass = configSql.get('dbPass'); +const cfgConnectionLimit = configSql.get('connectionlimit'); +const cfgTableResult = configSql.get('tableResult'); +const cfgDamengExtraOptions = configSql.get('damengExtraOptions'); +const forceClosingCountdownMs = 2000; + +// dmdb driver separates PoolAttributes and ConnectionAttributes. +// For some reason if you use pool you must define connection attributes in connectString, they are not included in config object, and pool.getConnection() can't configure it. +const poolHostInfo = `dm://${cfgDbUser}:${cfgDbPass}@${cfgDbHost}:${cfgDbPort}`; +const connectionOptions = Object.entries(cfgDamengExtraOptions).map(option => option.join('=')).join('&'); + +let pool = null; +const poolConfig = { + // String format dm://username:password@host:port[?prop1=val1[&prop2=val2]] + connectString: `${poolHostInfo}${connectionOptions.length > 0 ? '?' : ''}${connectionOptions}`, + poolMax: cfgConnectionLimit, + poolMin: 0 +}; + +function readLob(lob) { + return new Promise(function(resolve, reject) { + let blobData = Buffer.alloc(0); + let totalLength = 0; + + lob.on('data', function(chunk) { + totalLength += chunk.length; + blobData = Buffer.concat([blobData, chunk], totalLength); + }); + + lob.on('error', function(err) { + reject(err); + }); + + lob.on('end', function() { + resolve(blobData); + }); + }); +} + +async function formatResult(result) { + const res = []; + if (result?.rows && result?.metaData) { + for (let i = 0; i < result.rows.length; ++i) { + const row = result.rows[i]; + const out = {}; + for (let j = 0; j < result.metaData.length; ++j) { + let columnName = result.metaData[j].name; + if (row[j]?.on) { + const buf = await readLob(row[j]); + out[columnName] = buf.toString('utf8'); + } else { + out[columnName] = row[j]; + } + } + + res.push(out); + } + } + + return res; +} + +function sqlQuery(ctx, sqlCommand, callbackFunction, opt_noModifyRes = false, opt_noLog = false, opt_values = []) { + return executeQuery(ctx, sqlCommand, opt_values, opt_noModifyRes, opt_noLog).then( + result => callbackFunction?.(null, result), + error => callbackFunction?.(error) + ); +} + +async function executeQuery(ctx, sqlCommand, values = [], noModifyRes = false, noLog = false) { + let connection = null; + try { + if (!pool) { + pool = await db.createPool(poolConfig); + } + + connection = await pool.getConnection(); + const result = await connection.execute(sqlCommand, values, { resultSet: false }); + + let output = result; + if (!noModifyRes) { + if (result?.rows) { + output = await formatResult(result); + } else if (result?.rowsAffected) { + output = { affectedRows: result.rowsAffected }; + } else { + output = { rows: [], affectedRows: 0 }; + } + } + + return output; + } catch (error) { + if (!noLog) { + ctx.logger.warn('sqlQuery error sqlCommand: %s: %s', sqlCommand.slice(0, 50), error.stack); + } + + throw error; + } finally { + connection?.close(); + } +} + +function closePool() { + return pool.close(forceClosingCountdownMs); +} + +function addSqlParameter(val, values) { + values.push({ val: val }); + return `:${values.length}`; +} + +function concatParams(val1, val2) { + return `CONCAT(COALESCE(${val1}, ''), COALESCE(${val2}, ''))`; +} + +async function getTableColumns(ctx, tableName) { + let values = []; + let sqlParam = addSqlParameter(tableName.toUpperCase(), values); + const result = await executeQuery(ctx, `SELECT column_name FROM DBA_TAB_COLUMNS WHERE table_name = ${sqlParam};`, values); + return result.map(row => { return { column_name: row.column_name.toLowerCase() }}); +} + +async function upsert(ctx, task) { + task.completeDefaults(); + let dateNow = new Date(); + let values = []; + + let cbInsert = task.callback; + if (task.callback) { + let userCallback = new connectorUtilities.UserCallback(); + userCallback.fromValues(task.userIndex, task.callback); + cbInsert = userCallback.toSQLInsert(); + } + + const p0 = addSqlParameter(task.tenant, values); + const p1 = addSqlParameter(task.key, values); + const p2 = addSqlParameter(task.status, values); + const p3 = addSqlParameter(task.statusInfo, values); + const p4 = addSqlParameter(dateNow, values); + const p5 = addSqlParameter(task.userIndex, values); + const p6 = addSqlParameter(task.changeId, values); + const p7 = addSqlParameter(cbInsert, values); + const p8 = addSqlParameter(task.baseurl, values); + const p9 = addSqlParameter(dateNow, values); + + let sqlCommand = `MERGE INTO ${cfgTableResult} USING dual ON (tenant = ${p0} AND id = ${p1}) `; + sqlCommand += `WHEN NOT MATCHED THEN INSERT (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) `; + sqlCommand += `VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8}) `; + sqlCommand += `WHEN MATCHED THEN UPDATE SET last_open_date = ${p9}`; + + if (task.callback) { + let p10 = addSqlParameter(JSON.stringify(task.callback), values); + sqlCommand += `, callback = CONCAT(callback , '${connectorUtilities.UserCallback.prototype.delimiter}{"userIndex":' , (user_index + 1) , ',"callback":', ${p10}, '}')`; + } + + if (task.baseurl) { + let p11 = addSqlParameter(task.baseurl, values); + sqlCommand += `, baseurl = ${p11}`; + } + + sqlCommand += ', user_index = user_index + 1'; + sqlCommand += ';'; + sqlCommand += `SELECT user_index FROM ${cfgTableResult} WHERE tenant = ${p0} AND id = ${p1};`; + + const out = {}; + const result = await executeQuery(ctx, sqlCommand, values); + if (result?.length > 0) { + const first = result[0]; + out.isInsert = task.userIndex === first.user_index; + out.insertId = first.user_index; + } + + return out; +} + +module.exports = { + sqlQuery, + closePool, + addSqlParameter, + concatParams, + getTableColumns, + upsert +}; diff --git a/DocService/sources/databaseConnectors/mssqlConnector.js b/DocService/sources/databaseConnectors/mssqlConnector.js new file mode 100644 index 0000000000..8f745743eb --- /dev/null +++ b/DocService/sources/databaseConnectors/mssqlConnector.js @@ -0,0 +1,346 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const sql = require('mssql'); +const config = require('config'); +const connectorUtilities = require('./connectorUtilities'); +const utils = require('../../../Common/sources/utils'); + +const configSql = config.get('services.CoAuthoring.sql'); +const cfgTableResult = configSql.get('tableResult'); +const cfgTableChanges = configSql.get('tableChanges'); +const cfgMaxPacketSize = configSql.get('max_allowed_packet'); + +const connectionConfiguration = { + user: configSql.get('dbUser'), + password: configSql.get('dbPass'), + server: configSql.get('dbHost'), + port: parseInt(configSql.get('dbPort')), + database: configSql.get('dbName'), + pool: { + max: configSql.get('connectionlimit'), + min: 0 + } +}; +const additionalOptions = configSql.get('msSqlExtraOptions'); +const configuration = utils.deepMergeObjects({}, connectionConfiguration, additionalOptions); + +const placeholderPrefix = 'ph_'; + +function errorHandle(message, error, ctx) { + ctx.logger.error(`${message}:`); + + if (error.precedingErrors?.length) { + error.precedingErrors.forEach(category => ctx.logger.error(category.originalError)); + } else { + ctx.logger.error(error.originalError); + } +} + +function dataType(value) { + let type = sql.TYPES.NChar(1); + switch (typeof value) { + case 'number': { + type = sql.TYPES.Decimal(18, 0); + break; + } + case 'string': { + type = sql.TYPES.NVarChar(sql.MAX); + break; + } + case 'object': { + if (value instanceof Date) { + type = sql.TYPES.DateTime(); + } + + break; + } + } + + return type; +} + +function convertPlaceholdersValues(values) { + if (!Array.isArray(values)) { + return values instanceof Object ? values : {}; + } + + const placeholdersObject = {}; + for (const index in values) { + placeholdersObject[`${placeholderPrefix}${index}`] = values[index]; + } + + return placeholdersObject; +} + +function registerPlaceholderValues(values, statement) { + if (values._typesMetadata !== undefined) { + for (const placeholderName of Object.keys(values._typesMetadata)) { + statement.input(placeholderName, values._typesMetadata[placeholderName]); + } + + delete values._typesMetadata; + } else { + for (const key of Object.keys(values)) { + statement.input(key, dataType(values[key])); + } + } +} + +function sqlQuery(ctx, sqlCommand, callbackFunction, opt_noModifyRes = false, opt_noLog = false, opt_values = {}) { + return executeQuery(ctx, sqlCommand, opt_values, opt_noModifyRes, opt_noLog).then( + result => callbackFunction?.(null, result), + error => callbackFunction?.(error) + ); +} + +async function executeQuery(ctx, sqlCommand, values = {}, noModifyRes = false, noLog = false) { + try { + await sql.connect(configuration); + + const statement = new sql.PreparedStatement(); + const placeholders = convertPlaceholdersValues(values); + registerPlaceholderValues(placeholders, statement); + + await statement.prepare(sqlCommand); + const result = await statement.execute(placeholders); + await statement.unprepare(); + + if (!result.recordset && !result.rowsAffected?.length) { + return { rows: [], affectedRows: 0 }; + } + + let output = result; + if (!noModifyRes) { + if (result.recordset) { + output = result.recordset; + } else { + output = { affectedRows: result.rowsAffected.pop() }; + } + } + + return output; + } catch (error) { + if (!noLog) { + errorHandle(`sqlQuery() error while executing query: ${sqlCommand}`, error, ctx); + } + + throw error; + } +} + +async function executeBulk(ctx, table) { + try { + await sql.connect(configuration); + const result = await new sql.Request().bulk(table); + + return { affectedRows: result?.rowsAffected ?? 0 }; + } catch (error) { + errorHandle(`sqlQuery() error while executing bulk for table ${table.name}`, error, ctx); + + throw error; + } +} + +function closePool() { + return sql.close(); +} + +function addSqlParameterObjectBased(parameter, name, type, accumulatedObject) { + if (accumulatedObject._typesMetadata === undefined) { + accumulatedObject._typesMetadata = {}; + } + + const placeholder = `${placeholderPrefix}${name}`; + accumulatedObject[placeholder] = parameter; + accumulatedObject._typesMetadata[placeholder] = type; + + return `@${placeholder}`; +} + +function addSqlParameter(parameter, accumulatedArray) { + const currentIndex = accumulatedArray.push(parameter) - 1; + return `@${placeholderPrefix}${currentIndex}`; +} + +function concatParams(...parameters) { + return `CONCAT(${parameters.join(', ')})`; +} + +function getTableColumns(ctx, tableName) { + let values = []; + let sqlParam = addSqlParameter(tableName, values); + const sqlCommand = `SELECT column_name FROM information_schema.COLUMNS WHERE TABLE_NAME = ${sqlParam} AND TABLE_SCHEMA = 'dbo';`; + return executeQuery(ctx, sqlCommand, values); +} + +function getDocumentsWithChanges(ctx) { + const existingId = `SELECT TOP(1) id FROM ${cfgTableChanges} WHERE tenant=${cfgTableResult}.tenant AND id = ${cfgTableResult}.id`; + const sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE EXISTS(${existingId});`; + + return executeQuery(ctx, sqlCommand); +} + +function getExpired(ctx, maxCount, expireSeconds) { + const expireDate = new Date(); + utils.addSeconds(expireDate, -expireSeconds); + + const values = {}; + const date = addSqlParameterObjectBased(expireDate, 'expireDate', sql.TYPES.DateTime(), values); + const count = addSqlParameterObjectBased(maxCount, 'maxCount', sql.TYPES.Int(), values); + const notExistingTenantAndId = `SELECT TOP(1) tenant, id FROM ${cfgTableChanges} WHERE ${cfgTableChanges}.tenant = ${cfgTableResult}.tenant AND ${cfgTableChanges}.id = ${cfgTableResult}.id` + const sqlCommand = `SELECT TOP(${count}) * FROM ${cfgTableResult} WHERE last_open_date <= ${date} AND NOT EXISTS(${notExistingTenantAndId});`; + + return executeQuery(ctx, sqlCommand, values); +} + +async function upsert(ctx, task) { + task.completeDefaults(); + + let cbInsert = task.callback; + if (task.callback) { + const userCallback = new connectorUtilities.UserCallback(); + userCallback.fromValues(task.userIndex, task.callback); + cbInsert = userCallback.toSQLInsert(); + } + + const dateNow = new Date(); + + const values = {}; + const insertValuesPlaceholder = [ + addSqlParameterObjectBased(task.tenant, 'tenant', sql.TYPES.NVarChar(255), values), + addSqlParameterObjectBased(task.key, 'key', sql.TYPES.NVarChar(255), values), + addSqlParameterObjectBased(task.status, 'status', sql.TYPES.SmallInt(), values), + addSqlParameterObjectBased(task.statusInfo, 'statusInfo', sql.TYPES.Int(), values), + addSqlParameterObjectBased(dateNow, 'dateNow', sql.TYPES.DateTime(), values), + addSqlParameterObjectBased(task.userIndex, 'userIndex', sql.TYPES.Decimal(18, 0), values), + addSqlParameterObjectBased(task.changeId, 'changeId', sql.TYPES.Decimal(18, 0), values), + addSqlParameterObjectBased(cbInsert, 'cbInsert', sql.TYPES.NVarChar(sql.MAX), values), + addSqlParameterObjectBased(task.baseurl, 'baseurl', sql.TYPES.NVarChar(sql.MAX), values), + ]; + + const tenant = insertValuesPlaceholder[0]; + const id = insertValuesPlaceholder[1]; + const lastOpenDate = insertValuesPlaceholder[4]; + const baseUrl = insertValuesPlaceholder[8]; + const insertValues = insertValuesPlaceholder.join(', '); + const columns = ['tenant', 'id', 'status', 'status_info', 'last_open_date', 'user_index', 'change_id', 'callback', 'baseurl']; + const sourceColumns = columns.join(', '); + const sourceValues = columns.map(column => `source.${column}`).join(', '); + + const condition = `target.tenant = ${tenant} AND target.id = ${id}`; + let updateColumns = `target.last_open_date = ${lastOpenDate}`; + + if (task.callback) { + const parameter = addSqlParameterObjectBased(JSON.stringify(task.callback), 'callback', sql.TYPES.NVarChar(sql.MAX), values); + const concatenatedColumns = concatParams( + 'target.callback', `'${connectorUtilities.UserCallback.prototype.delimiter}{"userIndex":'`, '(target.user_index + 1)', `',"callback":'`, parameter, `'}'` + ); + + updateColumns += `, target.callback = ${concatenatedColumns}`; + } + + if (task.baseurl) { + updateColumns += `, target.baseurl = ${baseUrl}`; + } + + updateColumns += ', target.user_index = target.user_index + 1'; + + let sqlMerge = `MERGE INTO ${cfgTableResult} AS target ` + + `USING(VALUES(${insertValues})) AS source(${sourceColumns}) ` + + `ON(${condition}) ` + + `WHEN MATCHED THEN UPDATE SET ${updateColumns} ` + + `WHEN NOT MATCHED THEN INSERT(${sourceColumns}) VALUES(${sourceValues}) ` + + `OUTPUT $ACTION as action, INSERTED.user_index as insertId;`; + + const result = await executeQuery(ctx, sqlMerge, values, true); + const insertId = result.recordset[0].insertId; + const isInsert = result.recordset[0].action === 'INSERT'; + + return { isInsert, insertId }; +} + +function insertChanges(ctx, tableChanges, startIndex, objChanges, docId, index, user, callback) { + insertChangesAsync(ctx, tableChanges, startIndex, objChanges, docId, index, user).then( + result => callback(null, result, true), + error => callback(error, null, true) + ); +} + +async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, docId, index, user) { + if (startIndex === objChanges.length) { + return { affectedRows: 0 }; + } + + const table = new sql.Table(tableChanges); + table.columns.add('tenant', sql.TYPES.NVarChar(sql.MAX), { nullable: false, length: 'max' }); + table.columns.add('id', sql.TYPES.NVarChar(sql.MAX), { nullable: false, length: 'max' }); + table.columns.add('change_id', sql.TYPES.Int, { nullable: false }); + table.columns.add('user_id', sql.TYPES.NVarChar(sql.MAX), { nullable: false , length: 'max' }); + table.columns.add('user_id_original', sql.TYPES.NVarChar(sql.MAX), { nullable: false, length: 'max' }); + table.columns.add('user_name', sql.TYPES.NVarChar(sql.MAX), { nullable: false, length: 'max' }); + table.columns.add('change_data', sql.TYPES.NVarChar(sql.MAX), { nullable: false, length: 'max' }); + table.columns.add('change_date', sql.TYPES.DateTime, { nullable: false }); + + const indexBytes = 4; + const timeBytes = 8; + let bytes = 0; + let currentIndex = startIndex; + for (; currentIndex < objChanges.length && bytes <= cfgMaxPacketSize; ++currentIndex, ++index) { + bytes += indexBytes + timeBytes + + 4 * (ctx.tenant.length + docId.length + user.id.length + user.idOriginal.length + user.username.length + objChanges[currentIndex].change.length); + + table.rows.add(ctx.tenant, docId, index, user.id, user.idOriginal, user.username, objChanges[currentIndex].change, objChanges[currentIndex].time); + } + + const result = await executeBulk(ctx, table); + if (currentIndex < objChanges.length) { + const recursiveValue = await insertChangesAsync(ctx, tableChanges, currentIndex, objChanges, docId, index, user); + result.affectedRows += recursiveValue.affectedRows; + } + + return result; +} + +module.exports = { + sqlQuery, + closePool, + addSqlParameter, + concatParams, + getTableColumns, + getDocumentsWithChanges, + getExpired, + upsert, + insertChanges +}; diff --git a/DocService/sources/databaseConnectors/mysqlConnector.js b/DocService/sources/databaseConnectors/mysqlConnector.js new file mode 100644 index 0000000000..1c2103b52c --- /dev/null +++ b/DocService/sources/databaseConnectors/mysqlConnector.js @@ -0,0 +1,173 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const mysql = require('mysql2/promise'); +const connectorUtilities = require('./connectorUtilities'); +const config = require('config'); + +const configSql = config.get('services.CoAuthoring.sql'); +const cfgTableResult = configSql.get('tableResult'); + +const connectionConfiguration = { + host : configSql.get('dbHost'), + port : parseInt(configSql.get('dbPort')), + user : configSql.get('dbUser'), + password : configSql.get('dbPass'), + database : configSql.get('dbName'), + charset : configSql.get('charset'), + connectionLimit : configSql.get('connectionlimit'), + timezone : 'Z', + flags : '-FOUND_ROWS' +}; + +const additionalOptions = configSql.get('mysqlExtraOptions'); +const configuration = Object.assign({}, connectionConfiguration, additionalOptions); +let queryTimeout = undefined; +if (configuration.queryTimeout) { + queryTimeout = configuration.queryTimeout; + delete configuration.queryTimeout; +} + +const pool = mysql.createPool(configuration); + +function sqlQuery(ctx, sqlCommand, callbackFunction, opt_noModifyRes = false, opt_noLog = false, opt_values = []) { + return executeQuery(ctx, sqlCommand, opt_values, opt_noModifyRes, opt_noLog).then( + result => callbackFunction?.(null, result), + error => callbackFunction?.(error) + ); +} + +async function executeQuery(ctx, sqlCommand, values = [], noModifyRes = false, noLog = false) { + let connection = null; + try { + connection = await pool.getConnection(); + + const result = await connection.query({ sql: sqlCommand, timeout: queryTimeout, values }); + + let output; + if (!noModifyRes) { + output = result[0]?.affectedRows ? { affectedRows: result[0].affectedRows } : result[0]; + } else { + output = result[0]; + } + + return output ?? { rows: [], affectedRows: 0 }; + } catch (error) { + if (!noLog) { + ctx.logger.error(`sqlQuery() error while executing query: ${sqlCommand}\n${error.stack}`); + } + + throw error; + } finally { + if (connection) { + try { + // Put the connection back in the pool + connection.release(); + } catch (error) { + if (!noLog) { + ctx.logger.error(`connection.release() error while executing query: ${sqlCommand}\n${error.stack}`); + } + } + } + } +} + +async function closePool() { + return await pool.end(); +} + +function addSqlParameter(parameter, accumulatedArray) { + accumulatedArray.push(parameter); + return '?'; +} + +function concatParams(firstParameter, secondParameter) { + return `CONCAT(COALESCE(${firstParameter}, ''), COALESCE(${secondParameter}, ''))`; +} + +async function upsert(ctx, task) { + task.completeDefaults(); + const dateNow = new Date(); + + let cbInsert = task.callback; + if (task.callback) { + const userCallback = new connectorUtilities.UserCallback(); + userCallback.fromValues(task.userIndex, task.callback); + cbInsert = userCallback.toSQLInsert(); + } + + const values = []; + const valuesPlaceholder = [ + addSqlParameter(task.tenant, values), + addSqlParameter(task.key, values), + addSqlParameter(task.status, values), + addSqlParameter(task.statusInfo, values), + addSqlParameter(dateNow, values), + addSqlParameter(task.userIndex, values), + addSqlParameter(task.changeId, values), + addSqlParameter(cbInsert, values), + addSqlParameter(task.baseurl, values) + ]; + + let updateStatement = `last_open_date = ${addSqlParameter(dateNow, values)}`; + if (task.callback) { + let callbackPlaceholder = addSqlParameter(JSON.stringify(task.callback), values); + updateStatement += `, callback = CONCAT(callback , '${connectorUtilities.UserCallback.prototype.delimiter}{"userIndex":' , (user_index + 1) , ',"callback":', ${callbackPlaceholder}, '}')`; + } + + if (task.baseurl) { + let baseUrlPlaceholder = addSqlParameter(task.baseurl, values); + updateStatement += `, baseurl = ${baseUrlPlaceholder}`; + } + + updateStatement += ', user_index = LAST_INSERT_ID(user_index + 1);'; + + const sqlCommand = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) `+ + `VALUES (${valuesPlaceholder.join(', ')}) ` + + `ON DUPLICATE KEY UPDATE ${updateStatement}`; + + const result = await executeQuery(ctx, sqlCommand, values, true); + const insertId = result.affectedRows === 1 ? task.userIndex : result.insertId; + //if CLIENT_FOUND_ROWS don't specify 1 row is inserted , 2 row is updated, and 0 row is set to its current values + //http://dev.mysql.com/doc/refman/5.7/en/insert-on-duplicate.html + const isInsert = result.affectedRows === 1; + + return { isInsert, insertId }; +} + +module.exports.sqlQuery = sqlQuery; +module.exports.closePool = closePool; +module.exports.addSqlParameter = addSqlParameter; +module.exports.concatParams = concatParams; +module.exports.upsert = upsert; diff --git a/DocService/sources/databaseConnectors/oracleConnector.js b/DocService/sources/databaseConnectors/oracleConnector.js new file mode 100644 index 0000000000..153f1fc34c --- /dev/null +++ b/DocService/sources/databaseConnectors/oracleConnector.js @@ -0,0 +1,361 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const oracledb = require('oracledb'); +const config = require('config'); +const connectorUtilities = require('./connectorUtilities'); +const utils = require('../../../Common/sources/utils'); + +const configSql = config.get('services.CoAuthoring.sql'); +const cfgTableResult = configSql.get('tableResult'); +const cfgTableChanges = configSql.get('tableChanges'); +const cfgMaxPacketSize = configSql.get('max_allowed_packet'); + +const connectionConfiguration = { + user: configSql.get('dbUser'), + password: configSql.get('dbPass'), + connectString: `${configSql.get('dbHost')}:${configSql.get('dbPort')}/${configSql.get('dbName')}`, + poolMin: 0, + poolMax: configSql.get('connectionlimit') +}; +const additionalOptions = configSql.get('oracleExtraOptions'); +const configuration = Object.assign({}, connectionConfiguration, additionalOptions); +const forceClosingCountdownMs = 2000; +let pool = null; + +oracledb.fetchAsString = [ oracledb.NCLOB, oracledb.CLOB ]; +oracledb.autoCommit = true; + +function columnsToLowercase(rows) { + const formattedRows = []; + for (const row of rows) { + const newRow = {}; + for (const column in row) { + if (row.hasOwnProperty(column)) { + newRow[column.toLowerCase()] = row[column]; + } + } + + formattedRows.push(newRow); + } + + return formattedRows; +} + +function sqlQuery(ctx, sqlCommand, callbackFunction, opt_noModifyRes = false, opt_noLog = false, opt_values = []) { + return executeQuery(ctx, sqlCommand, opt_values, opt_noModifyRes, opt_noLog).then( + result => callbackFunction?.(null, result), + error => callbackFunction?.(error) + ); +} + +async function executeQuery(ctx, sqlCommand, values = [], noModifyRes = false, noLog = false) { + // Query must not have any ';' in oracle connector. + const correctedSql = sqlCommand.replace(/;/g, ''); + + let connection = null; + try { + if (!pool) { + pool = await oracledb.createPool(configuration); + } + + connection = await pool.getConnection(); + + const bondedValues = values ?? []; + const outputFormat = { outFormat: !noModifyRes ? oracledb.OUT_FORMAT_OBJECT : oracledb.OUT_FORMAT_ARRAY }; + const result = await connection.execute(correctedSql, bondedValues, outputFormat); + + let output = { rows: [], affectedRows: 0 }; + if (!noModifyRes) { + if (result?.rowsAffected) { + output = { affectedRows: result.rowsAffected }; + } + + if (result?.rows) { + output = columnsToLowercase(result.rows); + } + } else { + output = result; + } + + return output; + } catch (error) { + if (!noLog) { + ctx.logger.error(`sqlQuery() error while executing query: ${sqlCommand}\n${error.stack}`); + } + + throw error; + } finally { + if (connection) { + try { + // Put the connection back in the pool + await connection.close(); + } catch (error) { + if (!noLog) { + ctx.logger.error(`connection.close() error while executing query: ${sqlCommand}\n${error.stack}`); + } + } + } + } +} + +async function executeBunch(ctx, sqlCommand, values = [], noLog = false) { + let connection = null; + try { + if (!pool) { + pool = await oracledb.createPool(configuration); + } + + connection = await pool.getConnection(); + + const result = await connection.executeMany(sqlCommand, values); + + return { affectedRows: result?.rowsAffected ?? 0 }; + } catch (error) { + if (!noLog) { + ctx.logger.error(`sqlQuery() error while executing query: ${sqlCommand}\n${error.stack}`); + } + + throw error; + } finally { + connection?.close(); + } +} + +function closePool() { + return pool?.close(forceClosingCountdownMs); +} + +function healthCheck(ctx) { + return executeQuery(ctx, 'SELECT 1 FROM DUAL'); +} + +function addSqlParameter(parameter, accumulatedArray) { + const currentIndex = accumulatedArray.push(parameter) - 1; + return `:${currentIndex}`; +} + +function concatParams(firstParameter, secondParameter) { + return `${firstParameter} || ${secondParameter} || ''`; +} + +function getTableColumns(ctx, tableName) { + let values = []; + let sqlParam = addSqlParameter(tableName.toUpperCase(), values); + return executeQuery(ctx, `SELECT LOWER(column_name) AS column_name FROM user_tab_columns WHERE table_name = ${sqlParam}`, values); +} + +function getEmptyCallbacks(ctx) { + const joinCondition = 'ON t2.tenant = t1.tenant AND t2.id = t1.id AND t2.callback IS NULL'; + const sqlCommand = `SELECT DISTINCT t1.tenant, t1.id FROM ${cfgTableChanges} t1 INNER JOIN ${cfgTableResult} t2 ${joinCondition}`; + return executeQuery(ctx, sqlCommand); +} + +function getDocumentsWithChanges(ctx) { + const existingId = `SELECT id FROM ${cfgTableChanges} WHERE tenant=${cfgTableResult}.tenant AND id = ${cfgTableResult}.id AND ROWNUM <= 1`; + const sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE EXISTS(${existingId})`; + + return executeQuery(ctx, sqlCommand); +} + +function getExpired(ctx, maxCount, expireSeconds) { + const expireDate = new Date(); + utils.addSeconds(expireDate, -expireSeconds); + + const values = []; + const date = addSqlParameter(expireDate, values); + const count = addSqlParameter(maxCount, values); + const notExistingTenantAndId = `SELECT tenant, id FROM ${cfgTableChanges} WHERE ${cfgTableChanges}.tenant = ${cfgTableResult}.tenant AND ${cfgTableChanges}.id = ${cfgTableResult}.id AND ROWNUM <= 1` + const sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE last_open_date <= ${date} AND NOT EXISTS(${notExistingTenantAndId}) AND ROWNUM <= ${count}`; + + return executeQuery(ctx, sqlCommand, values); +} + +function makeUpdateSql(dateNow, task, values) { + const lastOpenDate = addSqlParameter(dateNow, values); + + let callback = ''; + if (task.callback) { + const parameter = addSqlParameter(JSON.stringify(task.callback), values); + callback = `, callback = callback || '${connectorUtilities.UserCallback.prototype.delimiter}{"userIndex":' || (user_index + 1) || ',"callback":' || ${parameter} || '}'`; + } + + let baseUrl = ''; + if (task.baseurl) { + const parameter = addSqlParameter(task.baseurl, values); + baseUrl = `, baseurl = ${parameter}`; + } + + const userIndex = ', user_index = user_index + 1'; + + const updateQuery = `last_open_date = ${lastOpenDate}${callback}${baseUrl}${userIndex}`; + const tenant = addSqlParameter(task.tenant, values); + const id = addSqlParameter(task.key, values); + const condition = `tenant = ${tenant} AND id = ${id}`; + + const returning = addSqlParameter({ type: oracledb.NUMBER, dir: oracledb.BIND_OUT }, values); + + return `UPDATE ${cfgTableResult} SET ${updateQuery} WHERE ${condition} RETURNING user_index INTO ${returning}`; +} + +function getReturnedValue(returned) { + return returned?.outBinds?.pop()?.pop(); +} + +async function upsert(ctx, task) { + task.completeDefaults(); + + let cbInsert = task.callback; + if (task.callback) { + const userCallback = new connectorUtilities.UserCallback(); + userCallback.fromValues(task.userIndex, task.callback); + cbInsert = userCallback.toSQLInsert(); + } + + const dateNow = new Date(); + + const insertValues = []; + const insertValuesPlaceholder = [ + addSqlParameter(task.tenant, insertValues), + addSqlParameter(task.key, insertValues), + addSqlParameter(task.status, insertValues), + addSqlParameter(task.statusInfo, insertValues), + addSqlParameter(dateNow, insertValues), + addSqlParameter(task.userIndex, insertValues), + addSqlParameter(task.changeId, insertValues), + addSqlParameter(cbInsert, insertValues), + addSqlParameter(task.baseurl, insertValues) + ]; + + const returned = addSqlParameter({ type: oracledb.NUMBER, dir: oracledb.BIND_OUT }, insertValues); + let sqlInsertTry = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) ` + + `VALUES(${insertValuesPlaceholder.join(', ')}) RETURNING user_index INTO ${returned}`; + + try { + const insertResult = await executeQuery(ctx, sqlInsertTry, insertValues, true, true); + const insertId = getReturnedValue(insertResult); + + return { isInsert: true, insertId }; + } catch (insertError) { + if (insertError.code !== 'ORA-00001') { + throw insertError; + } + + const values = []; + const updateResult = await executeQuery(ctx, makeUpdateSql(dateNow, task, values), values, true); + const insertId = getReturnedValue(updateResult); + + return { isInsert: false, insertId }; + } +} + +function insertChanges(ctx, tableChanges, startIndex, objChanges, docId, index, user, callback) { + insertChangesAsync(ctx, tableChanges, startIndex, objChanges, docId, index, user).then( + result => callback(null, result, true), + error => callback(error, null, true) + ); +} + +async function insertChangesAsync(ctx, tableChanges, startIndex, objChanges, docId, index, user) { + if (startIndex === objChanges.length) { + return { affectedRows: 0 }; + } + + const parametersCount = 8; + const maxPlaceholderLength = ':99'.length; + // (parametersCount - 1) - separator symbols length. + const maxInsertStatementLength = `INSERT /*+ APPEND_VALUES*/INTO ${tableChanges} VALUES()`.length + maxPlaceholderLength * parametersCount + (parametersCount - 1); + let packetCapacityReached = false; + + const values = []; + const indexBytes = 4; + const timeBytes = 8; + let lengthUtf8Current = 0; + let currentIndex = startIndex; + for (; currentIndex < objChanges.length; ++currentIndex, ++index) { + // 4 bytes is maximum for utf8 symbol. + const lengthUtf8Row = maxInsertStatementLength + indexBytes + timeBytes + + 4 * (ctx.tenant.length + docId.length + user.id.length + user.idOriginal.length + user.username.length + objChanges[currentIndex].change.length); + + if (lengthUtf8Row + lengthUtf8Current >= cfgMaxPacketSize && currentIndex > startIndex) { + packetCapacityReached = true; + break; + } + + const parameters = [ + ctx.tenant, + docId, + index, + user.id, + user.idOriginal, + user.username, + objChanges[currentIndex].change, + objChanges[currentIndex].time + ]; + + const rowValues = { ...parameters }; + + values.push(rowValues); + lengthUtf8Current += lengthUtf8Row; + } + + const placeholder = []; + for (let i = 0; i < parametersCount; i++) { + placeholder.push(`:${i}`); + } + + const sqlInsert = `INSERT /*+ APPEND_VALUES*/INTO ${tableChanges} VALUES(${placeholder.join(',')})`; + const result = await executeBunch(ctx, sqlInsert, values); + + if (packetCapacityReached) { + const recursiveValue = await insertChangesAsync(ctx, tableChanges, currentIndex, objChanges, docId, index, user); + result.affectedRows += recursiveValue.affectedRows; + } + + return result; +} + +module.exports = { + sqlQuery, + closePool, + healthCheck, + addSqlParameter, + concatParams, + getTableColumns, + getEmptyCallbacks, + getDocumentsWithChanges, + getExpired, + upsert, + insertChanges +} diff --git a/DocService/sources/databaseConnectors/postgreConnector.js b/DocService/sources/databaseConnectors/postgreConnector.js new file mode 100644 index 0000000000..329bfa749a --- /dev/null +++ b/DocService/sources/databaseConnectors/postgreConnector.js @@ -0,0 +1,246 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +var pg = require('pg'); +var co = require('co'); +var types = require('pg').types; +const connectorUtilities = require('./connectorUtilities'); +const operationContext = require('../../../Common/sources/operationContext'); +const config = require('config'); +var configSql = config.get('services.CoAuthoring.sql'); +const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult'); +var pgPoolExtraOptions = configSql.get('pgPoolExtraOptions'); +const cfgEditor = config.get('services.CoAuthoring.editor'); + +let connectionConfig = { + host: configSql.get('dbHost'), + port: parseInt(configSql.get('dbPort')), + user: configSql.get('dbUser'), + password: configSql.get('dbPass'), + database: configSql.get('dbName'), + max: configSql.get('connectionlimit'), + min: 0, + ssl: false +}; +//clone pgPoolExtraOptions to resolve 'TypeError: Cannot redefine property: key' in pg-pool +//timeouts from https://github.com/brianc/node-postgres/issues/3018#issuecomment-1619729794 +config.util.extendDeep(connectionConfig, pgPoolExtraOptions); +var pool = new pg.Pool(connectionConfig); +//listen "error" event otherwise - unhandled exception(https://github.com/brianc/node-postgres/issues/2764#issuecomment-1163475426) +pool.on('error', (err, client) => { + operationContext.global.logger.error(`postgresql pool error %s`, err.stack) +}) +//todo datetime timezone +pg.defaults.parseInputDatesAsUTC = true; +types.setTypeParser(1114, function(stringValue) { + return new Date(stringValue + '+0000'); +}); +types.setTypeParser(1184, function(stringValue) { + return new Date(stringValue + '+0000'); +}); + +var maxPacketSize = configSql.get('max_allowed_packet'); + +function sqlQuery(ctx, sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog, opt_values) { + co(function *() { + var result = null; + var error = null; + try { + result = yield pool.query(sqlCommand, opt_values); + } catch (err) { + error = err; + if (!opt_noLog) { + ctx.logger.warn('sqlQuery error sqlCommand: %s: %s', sqlCommand.slice(0, 50), err.stack); + } + } finally { + if (callbackFunction) { + var output = result; + if (result && !opt_noModifyRes) { + if ('SELECT' === result.command) { + output = result.rows; + } else { + output = {affectedRows: result.rowCount}; + } + } + callbackFunction(error, output); + } + } + }); +} + +function closePool() { + pool.end(); +} + +function addSqlParameter(val, values) { + values.push(val); + return '$' + values.length; +} + +function concatParams(val1, val2) { + return `COALESCE(${val1}, '') || COALESCE(${val2}, '')`; +} + +var isSupportOnConflict = true; + +function getUpsertString(task, values) { + task.completeDefaults(); + let dateNow = new Date(); + let cbInsert = task.callback; + if (isSupportOnConflict && task.callback) { + let userCallback = new connectorUtilities.UserCallback(); + userCallback.fromValues(task.userIndex, task.callback); + cbInsert = userCallback.toSQLInsert(); + } + let p0 = addSqlParameter(task.tenant, values); + let p1 = addSqlParameter(task.key, values); + let p2 = addSqlParameter(task.status, values); + let p3 = addSqlParameter(task.statusInfo, values); + let p4 = addSqlParameter(dateNow, values); + let p5 = addSqlParameter(task.userIndex, values); + let p6 = addSqlParameter(task.changeId, values); + let p7 = addSqlParameter(cbInsert, values); + let p8 = addSqlParameter(task.baseurl, values); + if (isSupportOnConflict) { + let p9 = addSqlParameter(dateNow, values); + //http://stackoverflow.com/questions/34762732/how-to-find-out-if-an-upsert-was-an-update-with-postgresql-9-5-upsert + let sqlCommand = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)`; + sqlCommand += ` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8})`; + sqlCommand += ` ON CONFLICT (tenant, id) DO UPDATE SET last_open_date = ${p9}`; + if (task.callback) { + let p10 = addSqlParameter(JSON.stringify(task.callback), values); + sqlCommand += `, callback = ${cfgTableResult}.callback || '${connectorUtilities.UserCallback.prototype.delimiter}{"userIndex":' `; + sqlCommand += ` || (${cfgTableResult}.user_index + 1)::text || ',"callback":' || ${p10}::text || '}'`; + } + if (task.baseurl) { + let p11 = addSqlParameter(task.baseurl, values); + sqlCommand += `, baseurl = ${p11}`; + } + sqlCommand += `, user_index = ${cfgTableResult}.user_index + 1 RETURNING user_index as userindex;`; + return sqlCommand; + } else { + return `SELECT * FROM merge_db(${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`; + } +} + +function upsert(ctx, task) { + return new Promise(function(resolve, reject) { + let values = []; + var sqlCommand = getUpsertString(task, values); + sqlQuery(ctx, sqlCommand, function(error, result) { + if (error) { + if (isSupportOnConflict && '42601' === error.code) { + //SYNTAX ERROR + isSupportOnConflict = false; + ctx.logger.warn('checkIsSupportOnConflict false'); + resolve(upsert(ctx, task)); + } else { + reject(error); + } + } else { + if (result && result.rows.length > 0) { + var first = result.rows[0]; + result = {}; + result.isInsert = task.userIndex === first.userindex; + result.insertId = first.userindex; + } + resolve(result); + } + }, true, undefined, values); + }); +} + +function insertChanges(ctx, tableChanges, startIndex, objChanges, docId, index, user, callback) { + let i = startIndex; + if (i >= objChanges.length) { + return; + } + let isSupported = true; + let tenant = []; + let id = []; + let changeId = []; + let userId = []; + let userIdOriginal = []; + let username = []; + let change = []; + let time = []; + //Postgres 9.4 multi-argument unnest + let sqlCommand = `INSERT INTO ${tableChanges} (tenant, id, change_id, user_id, user_id_original, user_name, change_data, change_date) `; + let changesType = cfgEditor['binaryChanges'] ? 'bytea' : 'text'; + sqlCommand += `SELECT * FROM UNNEST ($1::text[], $2::text[], $3::int[], $4::text[], $5::text[], $6::text[], $7::${changesType}[], $8::timestamp[]);`; + let values = [tenant, id, changeId, userId, userIdOriginal, username, change, time]; + let curLength = sqlCommand.length; + for (; i < objChanges.length; ++i) { + //4 is max utf8 bytes per symbol + curLength += 4 * (docId.length + user.id.length + user.idOriginal.length + user.username.length + objChanges[i].change.length) + 4 + 8; + if (curLength >= maxPacketSize && i > startIndex) { + sqlQuery(ctx, sqlCommand, function(error, output) { + if (error && '42883' == error.code) { + isSupported = false; + ctx.logger.warn('postgresql does not support UNNEST'); + } + if (error) { + callback(error, output, isSupported); + } else { + insertChanges(ctx, tableChanges, i, objChanges, docId, index, user, callback); + } + }, undefined, undefined, values); + return; + } + tenant.push(ctx.tenant); + id.push(docId); + changeId.push(index++); + userId.push(user.id); + userIdOriginal.push(user.idOriginal); + username.push(user.username); + change.push(objChanges[i].change); + time.push(objChanges[i].time); + } + sqlQuery(ctx, sqlCommand, function(error, output) { + if (error && '42883' == error.code) { + isSupported = false; + ctx.logger.warn('postgresql does not support UNNEST'); + } + callback(error, output, isSupported); + }, undefined, undefined, values); +} + +module.exports = { + sqlQuery, + closePool, + addSqlParameter, + concatParams, + upsert, + insertChanges +}; \ No newline at end of file diff --git a/DocService/sources/editorDataMemory.js b/DocService/sources/editorDataMemory.js new file mode 100644 index 0000000000..19c5adecd0 --- /dev/null +++ b/DocService/sources/editorDataMemory.js @@ -0,0 +1,508 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; +const config = require('config'); +const ms = require('ms'); +const utils = require('./../../Common/sources/utils'); +const commonDefines = require('./../../Common/sources/commondefines'); +const tenantManager = require('./../../Common/sources/tenantManager'); + +const cfgExpMonthUniqueUsers = ms(config.get('services.CoAuthoring.expire.monthUniqueUsers')); + +function EditorCommon() { + this.data = {}; +} +EditorCommon.prototype.connect = async function () {}; +EditorCommon.prototype.isConnected = function() { + return true; +}; +EditorCommon.prototype.ping = async function() {return "PONG"}; +EditorCommon.prototype.close = async function() {}; +EditorCommon.prototype.healthCheck = async function() { + if (this.isConnected()) { + await this.ping(); + return true; + } + return false; +}; +EditorCommon.prototype._getDocumentData = function(ctx, docId) { + let tenantData = this.data[ctx.tenant]; + if (!tenantData) { + this.data[ctx.tenant] = tenantData = {}; + } + let options = tenantData[docId]; + if (!options) { + tenantData[docId] = options = {}; + } + return options; +}; +EditorCommon.prototype._checkAndLock = function(ctx, name, docId, fencingToken, ttl) { + let data = this._getDocumentData(ctx, docId); + const now = Date.now(); + let res = true; + if (data[name] && now < data[name].expireAt && fencingToken !== data[name].fencingToken) { + res = false; + } else { + const expireAt = now + ttl * 1000; + data[name] = {fencingToken: fencingToken, expireAt: expireAt}; + } + return res; +}; +EditorCommon.prototype._checkAndUnlock = function(ctx, name, docId, fencingToken) { + let data = this._getDocumentData(ctx, docId); + const now = Date.now(); + let res; + if (data[name] && now < data[name].expireAt) { + if (fencingToken === data[name].fencingToken) { + res = commonDefines.c_oAscUnlockRes.Unlocked; + delete data[name]; + } else { + res = commonDefines.c_oAscUnlockRes.Locked; + } + } else { + res = commonDefines.c_oAscUnlockRes.Empty; + delete data[name]; + } + return res; +}; + +function EditorData() { + EditorCommon.call(this); + this.forceSaveTimer = {}; +} +EditorData.prototype = Object.create(EditorCommon.prototype); +EditorData.prototype.constructor = EditorData; + +EditorData.prototype.addPresence = async function(ctx, docId, userId, userInfo) {}; +EditorData.prototype.updatePresence = async function(ctx, docId, userId) {}; +EditorData.prototype.removePresence = async function(ctx, docId, userId) {}; +EditorData.prototype.getPresence = async function(ctx, docId, connections) { + let hvals = []; + if (connections) { + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + if (conn.docId === docId && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { + hvals.push(utils.getConnectionInfoStr(conn)); + } + } + } + return hvals; +}; + +EditorData.prototype.lockSave = async function(ctx, docId, userId, ttl) { + return this._checkAndLock(ctx, 'lockSave', docId, userId, ttl); +}; +EditorData.prototype.unlockSave = async function(ctx, docId, userId) { + return this._checkAndUnlock(ctx, 'lockSave', docId, userId); +}; +EditorData.prototype.lockAuth = async function(ctx, docId, userId, ttl) { + return this._checkAndLock(ctx, 'lockAuth', docId, userId, ttl); +}; +EditorData.prototype.unlockAuth = async function(ctx, docId, userId) { + return this._checkAndUnlock(ctx, 'lockAuth', docId, userId); +}; + +EditorData.prototype.getDocumentPresenceExpired = async function(now) { + return []; +}; +EditorData.prototype.removePresenceDocument = async function(ctx, docId) {}; + +EditorData.prototype.addLocks = async function(ctx, docId, locks) { + let data = this._getDocumentData(ctx, docId); + if (!data.locks) { + data.locks = {}; + } + Object.assign(data.locks, locks); +}; +EditorData.prototype.addLocksNX = async function(ctx, docId, locks) { + let data = this._getDocumentData(ctx, docId); + if (!data.locks) { + data.locks = {}; + } + let lockConflict = {}; + for (let lockId in locks) { + if (undefined === data.locks[lockId]) { + data.locks[lockId] = locks[lockId]; + } else { + lockConflict[lockId] = locks[lockId]; + } + } + return {lockConflict, allLocks: data.locks}; +}; +EditorData.prototype.removeLocks = async function(ctx, docId, locks) { + let data = this._getDocumentData(ctx, docId); + if (data.locks) { + for (let lockId in locks) { + delete data.locks[lockId]; + } + } +}; +EditorData.prototype.removeAllLocks = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + data.locks = undefined; +}; +EditorData.prototype.getLocks = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + return data.locks || {}; +}; + +EditorData.prototype.addMessage = async function(ctx, docId, msg) { + let data = this._getDocumentData(ctx, docId); + if (!data.messages) { + data.messages = []; + } + data.messages.push(msg); +}; +EditorData.prototype.removeMessages = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + data.messages = undefined; +}; +EditorData.prototype.getMessages = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + return data.messages || []; +}; + +EditorData.prototype.setSaved = async function(ctx, docId, status) { + let data = this._getDocumentData(ctx, docId); + data.saved = status; +}; +EditorData.prototype.getdelSaved = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + let res = data.saved; + data.saved = null; + return res; +}; +EditorData.prototype.setForceSave = async function(ctx, docId, time, index, baseUrl, changeInfo, convertInfo) { + let data = this._getDocumentData(ctx, docId); + data.forceSave = {time, index, baseUrl, changeInfo, started: false, ended: false, convertInfo}; +}; +EditorData.prototype.getForceSave = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + return data.forceSave || null; +}; +EditorData.prototype.checkAndStartForceSave = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + let res; + if (data.forceSave && !data.forceSave.started) { + data.forceSave.started = true; + data.forceSave.ended = false; + res = data.forceSave; + } + return res; +}; +EditorData.prototype.checkAndSetForceSave = async function(ctx, docId, time, index, started, ended, convertInfo) { + let data = this._getDocumentData(ctx, docId); + let res; + if (data.forceSave && time === data.forceSave.time && index === data.forceSave.index) { + data.forceSave.started = started; + data.forceSave.ended = ended; + data.forceSave.convertInfo = convertInfo; + res = data.forceSave; + } + return res; +}; +EditorData.prototype.removeForceSave = async function(ctx, docId) { + let data = this._getDocumentData(ctx, docId); + data.forceSave = undefined; +}; + +EditorData.prototype.cleanDocumentOnExit = async function(ctx, docId) { + let tenantData = this.data[ctx.tenant]; + if (tenantData) { + delete tenantData[docId]; + } + let tenantTimer = this.forceSaveTimer[ctx.tenant]; + if (tenantTimer) { + delete tenantTimer[docId]; + } +}; + +EditorData.prototype.addForceSaveTimerNX = async function(ctx, docId, expireAt) { + let tenantTimer = this.forceSaveTimer[ctx.tenant]; + if (!tenantTimer) { + this.forceSaveTimer[ctx.tenant] = tenantTimer = {}; + } + if (!tenantTimer[docId]) { + tenantTimer[docId] = expireAt; + } +}; +EditorData.prototype.getForceSaveTimer = async function(now) { + let res = []; + for (let tenant in this.forceSaveTimer) { + if (this.forceSaveTimer.hasOwnProperty(tenant)) { + let tenantTimer = this.forceSaveTimer[tenant]; + for (let docId in tenantTimer) { + if (tenantTimer.hasOwnProperty(docId)) { + if (tenantTimer[docId] < now) { + res.push([tenant, docId]); + delete tenantTimer[docId]; + } + } + } + } + } + return res; +}; + +function EditorStat() { + EditorCommon.call(this); + this.uniqueUser = {}; + this.uniqueUsersOfMonth = {}; + this.uniqueViewUser = {}; + this.uniqueViewUsersOfMonth = {}; + this.stat = {}; + this.shutdown = {}; + this.license = {}; +} +EditorStat.prototype = Object.create(EditorCommon.prototype); +EditorStat.prototype.constructor = EditorStat; +EditorStat.prototype.addPresenceUniqueUser = async function(ctx, userId, expireAt, userInfo) { + let tenantUser = this.uniqueUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueUser[ctx.tenant] = tenantUser = {}; + } + tenantUser[userId] = {expireAt: expireAt, userInfo: userInfo}; +}; +EditorStat.prototype.getPresenceUniqueUser = async function(ctx, nowUTC) { + let res = []; + let tenantUser = this.uniqueUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueUser[ctx.tenant] = tenantUser = {}; + } + for (let userId in tenantUser) { + if (tenantUser.hasOwnProperty(userId)) { + if (tenantUser[userId].expireAt > nowUTC) { + let elem = tenantUser[userId]; + let newElem = {userid: userId, expire: new Date(elem.expireAt * 1000)}; + Object.assign(newElem, elem.userInfo); + res.push(newElem); + } else { + delete tenantUser[userId]; + } + } + } + return res; +}; +EditorStat.prototype.addPresenceUniqueUsersOfMonth = async function(ctx, userId, period, userInfo) { + let tenantUser = this.uniqueUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueUsersOfMonth[ctx.tenant] = tenantUser = {}; + } + if(!tenantUser[period]) { + let expireAt = Date.now() + cfgExpMonthUniqueUsers; + tenantUser[period] = {expireAt: expireAt, data: {}}; + } + tenantUser[period].data[userId] = userInfo; +}; +EditorStat.prototype.getPresenceUniqueUsersOfMonth = async function(ctx) { + let res = {}; + let nowUTC = Date.now(); + let tenantUser = this.uniqueUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueUsersOfMonth[ctx.tenant] = tenantUser = {}; + } + for (let periodId in tenantUser) { + if (tenantUser.hasOwnProperty(periodId)) { + if (tenantUser[periodId].expireAt <= nowUTC) { + delete tenantUser[periodId]; + } else { + let date = new Date(parseInt(periodId)).toISOString(); + res[date] = tenantUser[periodId].data; + } + } + } + return res; +}; + +EditorStat.prototype.addPresenceUniqueViewUser = async function(ctx, userId, expireAt, userInfo) { + let tenantUser = this.uniqueViewUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUser[ctx.tenant] = tenantUser = {}; + } + tenantUser[userId] = {expireAt: expireAt, userInfo: userInfo}; +}; +EditorStat.prototype.getPresenceUniqueViewUser = async function(ctx, nowUTC) { + let res = []; + let tenantUser = this.uniqueViewUser[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUser[ctx.tenant] = tenantUser = {}; + } + for (let userId in tenantUser) { + if (tenantUser.hasOwnProperty(userId)) { + if (tenantUser[userId].expireAt > nowUTC) { + let elem = tenantUser[userId]; + let newElem = {userid: userId, expire: new Date(elem.expireAt * 1000)}; + Object.assign(newElem, elem.userInfo); + res.push(newElem); + } else { + delete tenantUser[userId]; + } + } + } + return res; +}; +EditorStat.prototype.addPresenceUniqueViewUsersOfMonth = async function(ctx, userId, period, userInfo) { + let tenantUser = this.uniqueViewUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUsersOfMonth[ctx.tenant] = tenantUser = {}; + } + if(!tenantUser[period]) { + let expireAt = Date.now() + cfgExpMonthUniqueUsers; + tenantUser[period] = {expireAt: expireAt, data: {}}; + } + tenantUser[period].data[userId] = userInfo; +}; +EditorStat.prototype.getPresenceUniqueViewUsersOfMonth = async function(ctx) { + let res = {}; + let nowUTC = Date.now(); + let tenantUser = this.uniqueViewUsersOfMonth[ctx.tenant]; + if (!tenantUser) { + this.uniqueViewUsersOfMonth[ctx.tenant] = tenantUser = {}; + } + for (let periodId in tenantUser) { + if (tenantUser.hasOwnProperty(periodId)) { + if (tenantUser[periodId].expireAt <= nowUTC) { + delete tenantUser[periodId]; + } else { + let date = new Date(parseInt(periodId)).toISOString(); + res[date] = tenantUser[periodId].data; + } + } + } + return res; +}; +EditorStat.prototype.setEditorConnections = async function(ctx, countEdit, countLiveView, countView, now, precision) { + let tenantStat = this.stat[ctx.tenant]; + if (!tenantStat) { + this.stat[ctx.tenant] = tenantStat = []; + } + tenantStat.push({time: now, edit: countEdit, liveview: countLiveView, view: countView}); + let i = 0; + while (i < tenantStat.length && tenantStat[i] < now - precision[precision.length - 1].val) { + i++; + } + tenantStat.splice(0, i); +}; +EditorStat.prototype.getEditorConnections = async function(ctx) { + let tenantStat = this.stat[ctx.tenant]; + if (!tenantStat) { + this.stat[ctx.tenant] = tenantStat = []; + } + return tenantStat; +}; +EditorStat.prototype.setEditorConnectionsCountByShard = async function(ctx, shardId, count) {}; +EditorStat.prototype.incrEditorConnectionsCountByShard = async function(ctx, shardId, count) {}; +EditorStat.prototype.getEditorConnectionsCount = async function(ctx, connections) { + let count = 0; + if (connections) { + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + if (!(conn.isCloseCoAuthoring || (conn.user && conn.user.view)) && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { + count++; + } + } + } + return count; +}; +EditorStat.prototype.setViewerConnectionsCountByShard = async function(ctx, shardId, count) {}; +EditorStat.prototype.incrViewerConnectionsCountByShard = async function(ctx, shardId, count) {}; +EditorStat.prototype.getViewerConnectionsCount = async function(ctx, connections) { + let count = 0; + if (connections) { + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + if (conn.isCloseCoAuthoring || (conn.user && conn.user.view) && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { + count++; + } + } + } + return count; +}; +EditorStat.prototype.setLiveViewerConnectionsCountByShard = async function(ctx, shardId, count) {}; +EditorStat.prototype.incrLiveViewerConnectionsCountByShard = async function(ctx, shardId, count) {}; +EditorStat.prototype.getLiveViewerConnectionsCount = async function(ctx, connections) { + let count = 0; + if (connections) { + for (let i = 0; i < connections.length; ++i) { + let conn = connections[i]; + if (utils.isLiveViewer(conn) && ctx.tenant === tenantManager.getTenantByConnection(ctx, conn)) { + count++; + } + } + } + return count; +}; +EditorStat.prototype.addShutdown = async function(key, docId) { + if (!this.shutdown[key]) { + this.shutdown[key] = {}; + } + this.shutdown[key][docId] = 1; +}; +EditorStat.prototype.removeShutdown = async function(key, docId) { + if (!this.shutdown[key]) { + this.shutdown[key] = {}; + } + delete this.shutdown[key][docId]; +}; +EditorStat.prototype.getShutdownCount = async function(key) { + let count = 0; + if (this.shutdown[key]) { + for (let docId in this.shutdown[key]) { + if (this.shutdown[key].hasOwnProperty(docId)) { + count++; + } + } + } + return count; +}; +EditorStat.prototype.cleanupShutdown = async function(key) { + delete this.shutdown[key]; +}; +EditorStat.prototype.setLicense = async function(key, val) { + this.license[key] = val; +}; +EditorStat.prototype.getLicense = async function(key) { + return this.license[key] || null; +}; +EditorStat.prototype.removeLicense = async function(key) { + delete this.license[key]; +}; +EditorStat.prototype.lockNotification = async function(ctx, notificationType, ttl) { + //true NaN !== NaN + return this._checkAndLock(ctx, notificationType, notificationType, NaN, ttl); +}; + +module.exports = { + EditorData, + EditorStat +} diff --git a/DocService/sources/fileuploaderservice.js b/DocService/sources/fileuploaderservice.js index b5c3080a0a..8a61be7a13 100644 --- a/DocService/sources/fileuploaderservice.js +++ b/DocService/sources/fileuploaderservice.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -31,219 +31,133 @@ */ 'use strict'; - +const crypto = require('crypto'); var multiparty = require('multiparty'); var co = require('co'); -var jwt = require('jsonwebtoken'); -var taskResult = require('./taskresult'); +const utilsDocService = require('./utilsDocService'); var docsCoServer = require('./DocsCoServer'); var utils = require('./../../Common/sources/utils'); var constants = require('./../../Common/sources/constants'); var storageBase = require('./../../Common/sources/storage-base'); var formatChecker = require('./../../Common/sources/formatchecker'); -var logger = require('./../../Common/sources/logger'); +const commonDefines = require('./../../Common/sources/commondefines'); +const operationContext = require('./../../Common/sources/operationContext'); var config = require('config'); -var configServer = config.get('services.CoAuthoring.server'); -var configUtils = config.get('services.CoAuthoring.utils'); -var cfgImageSize = configServer.get('limits_image_size'); -var cfgTypesUpload = configUtils.get('limits_image_types_upload'); -var cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); -var cfgTokenEnableRequestInbox = config.get('services.CoAuthoring.token.enable.request.inbox'); +const cfgImageSize = config.get('services.CoAuthoring.server.limits_image_size'); +const cfgTypesUpload = config.get('services.CoAuthoring.utils.limits_image_types_upload'); +const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); -exports.uploadTempFile = function(req, res) { - return co(function* () { - var docId = 'null'; - try { - docId = req.query.key; - logger.debug('Start uploadTempFile: docId = %s', docId); - if (cfgTokenEnableRequestInbox) { - var authError = constants.VKEY; - var checkJwtRes = docsCoServer.checkJwtHeader(docId, req); - if (checkJwtRes) { - if (checkJwtRes.decoded) { - authError = constants.NO_ERROR; - if (checkJwtRes.decoded.query && checkJwtRes.decoded.query.key) { - docId = checkJwtRes.decoded.query.key; - } - if (checkJwtRes.decoded.payloadhash && - !docsCoServer.checkJwtPayloadHash(docId, checkJwtRes.decoded.payloadhash, req.body, checkJwtRes.token)) { - authError = constants.VKEY; - } - } else { - if (constants.JWT_EXPIRED_CODE == checkJwtRes.code) { - authError = constants.VKEY_KEY_EXPIRE; - } - } - } - if (authError !== constants.NO_ERROR) { - utils.fillResponse(req, res, undefined, authError); - return; - } - } +const PATTERN_ENCRYPTED = 'ENCRYPTED;'; - if (docId && req.body && Buffer.isBuffer(req.body)) { - var task = yield* taskResult.addRandomKeyTask(docId); - var strPath = task.key + '/' + docId + '.tmp'; - yield storageBase.putObject(strPath, req.body, req.body.length); - var url = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath); - utils.fillResponse(req, res, url, constants.NO_ERROR); - } else { - utils.fillResponse(req, res, undefined, constants.UNKNOWN); - } - logger.debug('End uploadTempFile: docId = %s', docId); - } - catch (e) { - logger.error('Error uploadTempFile: docId = %s\r\n%s', docId, e.stack); - utils.fillResponse(req, res, undefined, constants.UNKNOWN); - } - }); -}; -function checkJwtUpload(docId, errorName, token){ - var res = {err: true, docId: null, userid: null}; - var checkJwtRes = docsCoServer.checkJwt(docId, token, true); +function* checkJwtUpload(ctx, errorName, token){ + let checkJwtRes = yield docsCoServer.checkJwt(ctx, token, commonDefines.c_oAscSecretType.Session); + return checkJwtUploadTransformRes(ctx, errorName, checkJwtRes); +} +function checkJwtUploadTransformRes(ctx, errorName, checkJwtRes){ + var res = {err: true, docId: null, userid: null, encrypted: null}; if (checkJwtRes.decoded) { var doc = checkJwtRes.decoded.document; var edit = checkJwtRes.decoded.editorConfig; - if (!edit.ds_view && !edit.ds_isCloseCoAuthoring) { + //todo check view and pdf editor (temporary fix) + if (!edit.ds_isCloseCoAuthoring) { res.err = false; res.docId = doc.key; + res.encrypted = doc.ds_encrypted; if (edit.user) { res.userid = edit.user.id; } } else { - logger.error('Error %s jwt: docId = %s\r\n%s', errorName, docId, 'access deny'); + ctx.logger.warn('Error %s jwt: %s', errorName, 'access deny'); } } else { - logger.error('Error %s jwt: docId = %s\r\n%s', errorName, docId, checkJwtRes.description); + ctx.logger.warn('Error %s jwt: %s', errorName, checkJwtRes.description); } return res; } -exports.uploadImageFileOld = function(req, res) { - var docId = req.params.docid; - logger.debug('Start uploadImageFileOld: docId = %s', docId); - var userid = req.params.userid; - if (cfgTokenEnableBrowser) { - var checkJwtRes = checkJwtUpload(docId, 'uploadImageFileOld', req.params.jwt); - if(!checkJwtRes.err){ - docId = checkJwtRes.docId || docId; - userid = checkJwtRes.userid || userid; - } else { - res.sendStatus(400); - return; - } - } - var index = parseInt(req.params.index); - var listImages = []; - //todo userid - if (docId && index) { - var isError = false; - var form = new multiparty.Form(); - form.on('error', function(err) { - logger.error('Error parsing form: docId = %s\r\n%s', docId, err.toString()); - res.sendStatus(400); - }); - form.on('part', function(part) { - if (!part.filename) { - // ignore field's content - part.resume(); - } - if (part.filename) { - if (part.byteCount > cfgImageSize) { - isError = true; - } - if (isError) { - part.resume(); - } else { - //в начале пишетÑÑ Ñ…ÐµÑˆ, чтобы избежать ошибок при параллельном upload в ÑовмеÑтном редактировании - var strImageName = utils.crc32(userid).toString(16) + '_image' + (parseInt(index) + listImages.length); - var strPath = docId + '/media/' + strImageName + '.jpg'; - listImages.push(strPath); - utils.stream2Buffer(part).then(function(buffer) { - return storageBase.putObject(strPath, buffer, buffer.length); - }).then(function() { - part.resume(); - }).catch(function(err) { - logger.error('Upload putObject: docId = %s\r\n%s', docId, err.stack); - isError = true; - part.resume(); - }); - } - } - part.on('error', function(err) { - logger.error('Error parsing form part: docId = %s\r\n%s', docId, err.toString()); - }); - }); - form.on('close', function() { - if (isError) { - res.sendStatus(400); - } else { - storageBase.getSignedUrlsByArray(utils.getBaseUrlByRequest(req), listImages, docId).then(function(urls) { - var outputData = {'type': 0, 'error': constants.NO_ERROR, 'urls': urls, 'input': req.query}; - var output = ''; - - //res.setHeader('Access-Control-Allow-Origin', '*'); - res.send(output); - logger.debug('End uploadImageFileOld: docId = %s %s', docId, output); - } - ).catch(function(err) { - res.sendStatus(400); - logger.error('upload getSignedUrlsByArray: docId = %s\r\n%s', docId, err.stack); - }); - } - }); - form.parse(req); - } else { - logger.debug('Error params uploadImageFileOld: docId = %s', docId); - res.sendStatus(400); - } -}; exports.uploadImageFile = function(req, res) { return co(function* () { - var isError = true; + let httpStatus = 200; var docId = 'null'; + let output = {}; + let ctx = new operationContext.Context(); try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); docId = req.params.docid; - var userid = req.params.userid; - logger.debug('Start uploadImageFile: docId = %s', docId); + ctx.setDocId(docId); + let encrypted = false; + ctx.logger.debug('Start uploadImageFile'); + const tenImageSize = ctx.getCfg('services.CoAuthoring.server.limits_image_size', cfgImageSize); + const tenTypesUpload = ctx.getCfg('services.CoAuthoring.utils.limits_image_types_upload', cfgTypesUpload); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); - var isValidJwt = true; - if (cfgTokenEnableBrowser) { - var checkJwtRes = checkJwtUpload(docId, 'uploadImageFile', req.params.jwt); - if (!checkJwtRes.err) { - docId = checkJwtRes.docId || docId; - userid = checkJwtRes.userid || userid; + if (tenTokenEnableBrowser) { + let checkJwtRes = yield docsCoServer.checkJwtHeader(ctx, req, 'Authorization', 'Bearer ', commonDefines.c_oAscSecretType.Session); + if (!checkJwtRes) { + //todo remove compatibility with previous versions + checkJwtRes = yield docsCoServer.checkJwt(ctx, req.query['token'], commonDefines.c_oAscSecretType.Session); + } + let transformedRes = checkJwtUploadTransformRes(ctx, 'uploadImageFile', checkJwtRes); + if (!transformedRes.err) { + docId = transformedRes.docId || docId; + encrypted = transformedRes.encrypted; + ctx.setDocId(docId); + ctx.setUserId(transformedRes.userid); } else { - isValidJwt = false; + httpStatus = 403; } } - var index = parseInt(req.params.index); - if (isValidJwt && docId && req.body && Buffer.isBuffer(req.body)) { - var buffer = req.body; - var format = formatChecker.getImageFormat(buffer); - var formatStr = formatChecker.getStringFromFormat(format); - var supportedFormats = cfgTypesUpload || 'jpg'; - if (formatStr && -1 !== supportedFormats.indexOf(formatStr) && buffer.length <= cfgImageSize) { - //в начале пишетÑÑ Ñ…ÐµÑˆ, чтобы избежать ошибок при параллельном upload в ÑовмеÑтном редактировании - var strImageName = utils.crc32(userid).toString(16) + '_image' + index; - var strPathRel = 'media/' + strImageName + '.' + formatStr; - var strPath = docId + '/' + strPathRel; - yield storageBase.putObject(strPath, buffer, buffer.length); - var output = {}; - output[strPathRel] = yield storageBase.getSignedUrl(utils.getBaseUrlByRequest(req), strPath); - res.send(JSON.stringify(output)); - isError = false; + if (200 === httpStatus && docId && req.body && Buffer.isBuffer(req.body)) { + let buffer = req.body; + if (buffer.length <= tenImageSize) { + var format = formatChecker.getImageFormat(ctx, buffer); + var formatStr = formatChecker.getStringFromFormat(format); + if (encrypted && PATTERN_ENCRYPTED === buffer.toString('utf8', 0, PATTERN_ENCRYPTED.length)) { + formatStr = buffer.toString('utf8', PATTERN_ENCRYPTED.length, buffer.indexOf(';', PATTERN_ENCRYPTED.length)); + } + var supportedFormats = tenTypesUpload || 'jpg'; + let formatLimit = formatStr && -1 !== supportedFormats.indexOf(formatStr); + if (formatLimit) { + if (format === constants.AVS_OFFICESTUDIO_FILE_IMAGE_TIFF) { + buffer = yield utilsDocService.convertImageToPng(ctx, buffer); + format = constants.AVS_OFFICESTUDIO_FILE_IMAGE_PNG; + formatStr = formatChecker.getStringFromFormat(format); + } + //a hash is written at the beginning to avoid errors during parallel upload in co-editing + var strImageName = crypto.randomBytes(16).toString("hex"); + var strPathRel = 'media/' + strImageName + '.' + formatStr; + var strPath = docId + '/' + strPathRel; + + buffer = yield utilsDocService.fixImageExifRotation(ctx, buffer); + + yield storageBase.putObject(ctx, strPath, buffer, buffer.length); + output[strPathRel] = yield storageBase.getSignedUrl(ctx, utils.getBaseUrlByRequest(ctx, req), strPath, + commonDefines.c_oAscUrlTypes.Session); + } else { + httpStatus = 415; + ctx.logger.debug('uploadImageFile format is not supported'); + } + } else { + httpStatus = 413; + ctx.logger.debug('uploadImageFile size limit exceeded: buffer.length = %d', buffer.length); } } - logger.debug('End uploadImageFile: isError = %d docId = %s', isError, docId); } catch (e) { - logger.error('Error uploadImageFile: docId = %s\r\n%s', docId, e.stack); + httpStatus = 400; + ctx.logger.error('Error uploadImageFile:%s', e.stack); } finally { - if (isError) { - res.sendStatus(400); + try { + if (200 === httpStatus) { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(output)); + } else { + res.sendStatus(httpStatus); + } + ctx.logger.debug('End uploadImageFile: httpStatus = %d', httpStatus); + } catch (e) { + ctx.logger.error('Error uploadImageFile:%s', e.stack); } } }); diff --git a/DocService/sources/fontservice.js b/DocService/sources/fontservice.js deleted file mode 100644 index 78587db708..0000000000 --- a/DocService/sources/fontservice.js +++ /dev/null @@ -1,291 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * Pursuant to Section 7(b) of the License you must retain the original Product - * logo when distributing the program. Pursuant to Section 7(e) we decline to - * grant you any rights under trademark law for use of our trademarks. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing 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 - * - */ - -'use strict'; - -var fs = require('fs'); -var path = require('path'); -var util = require('util'); -var transform = require('stream').Transform; -var base64 = require('base64-stream'); -var mime = require('mime'); -var co = require('co'); -var utils = require('./../../Common/sources/utils'); -var logger = require('./../../Common/sources/logger'); -var statsDClient = require('./../../Common/sources/statsdclient'); -var config = require('config').get('services.CoAuthoring.utils'); - -var cfgFontDir = config.get('utils_common_fontdir'); -var cfgSearchPatterns = config.get('utils_fonts_search_patterns'); -var cfgResourceExpires = config.get('resource_expires'); - -var JS_EXTENTION = '.js'; -var BYTE_MAX_VALUE = 255; -var GUID_ODTTF = [0xA0, 0x66, 0xD6, 0x20, 0x14, 0x96, 0x47, 0xfa, 0x95, 0x69, 0xB8, 0x50, 0xB0, 0x41, 0x49, 0x48]; - -var fontNameToFullPath = {}; -var clientStatsD = statsDClient.getClient(); - -function ZBase32Encoder() { - this.encodingTable = 'ybndrfg8ejkmcpqxot1uwisza345h769'; - this.decodingTable = new Uint8Array(128); - var i; - for (i = 0; i < this.decodingTable.length; ++i) { - this.decodingTable[i] = BYTE_MAX_VALUE; - } - for (i = 0; i < this.encodingTable.length; ++i) { - this.decodingTable[this.encodingTable.charCodeAt(i)] = i; - } -} -ZBase32Encoder.prototype = { - decode: function(data) { - if (!data) { - return ''; - } - var result = new Buffer(Math.floor(data.length * 5.0 / 8.0)); - var resultIndex = 0; - var index = new Int8Array(8); - var dataContainer = {data: data}; - for (var i = 0; i < data.length;) { - - i = this.createIndexByOctetAndMovePosition(dataContainer, i, index); - - var shortByteCount = 0; - //avoid Bitwise Operators because buffer became (2^40) - //JavaScript converts operands to 32-bit signed ints before doing bitwise operations - var buffer = 0; - for (var j = 0; j < 8 && index[j] != -1; ++j) { - buffer = (buffer * 32) + (this.decodingTable[index[j]] & 0x1f); - shortByteCount++; - } - var bitCount = shortByteCount * 5; - while (bitCount >= 8) { - result[resultIndex++] = (buffer / Math.pow(2, bitCount - 8)) & 0xff; - bitCount -= 8; - } - } - return result.toString('utf8', 0, resultIndex); - }, - createIndexByOctetAndMovePosition: function(container, currentPosition, index) { - var j = 0; - while (j < 8) { - if (currentPosition >= container.data.length) { - index[j++] = -1; - continue; - } - - if (this.ignoredSymbol(container.data.charCodeAt(currentPosition))) { - currentPosition++; - continue; - } - - index[j] = container.data.charCodeAt(currentPosition); - j++; - currentPosition++; - } - - return currentPosition; - }, - ignoredSymbol: function(checkedSymbol) { - return checkedSymbol >= this.decodingTable.length || this.decodingTable[checkedSymbol] == BYTE_MAX_VALUE; - } -}; - -function OdttfProtocol(options, fileSize) { - if (!(this instanceof OdttfProtocol)) { - return new OdttfProtocol(options); - } - transform.call(this, options); - this._inBody = false; - this._rawHeaderLength = 0; - this._rawHeader = []; - this._threshold = Math.min(2 * GUID_ODTTF.length, fileSize); -} -util.inherits(OdttfProtocol, transform); -OdttfProtocol.prototype._transform = function(chunk, encoding, done) { - if (!this._inBody) { - this._rawHeaderLength += chunk.length; - if (this._rawHeaderLength >= this._threshold) { - var data; - if (this._rawHeader.length > 0) { - this._rawHeader.push(chunk); - data = Buffer.concat(this._rawHeader); - } else { - data = chunk; - } - for (var i = 0; i < this._threshold; ++i) { - data[i] ^= GUID_ODTTF[i % 16]; - } - this.push(data); - this._inBody = true; - } else { - this._rawHeader.push(chunk); - } - } else { - this.push(chunk); - } - done(); -}; - -function* initFontMapByFolder(fontDir, patterns) { - //todo remove replace - patterns = patterns.replace(/\*/g, ''); - var searchPatterns = patterns.split(/[|,;]/); - var files = yield utils.listObjects(fontDir); - for (var i = 0; i < files.length; ++i) { - var file = files[i]; - if (-1 != searchPatterns.indexOf(path.extname(file).toLowerCase())) { - fontNameToFullPath[path.basename(file).toLowerCase()] = file; - } - } -} -function getJsContent(res, fileStream, fileSize, filename) { - return new Promise(function(resolve, reject) { - res.write(new Buffer('window["' + filename + '"] = "' + fileSize + ';', 'utf8')); - var tmpStream = fileStream.pipe(base64.encode()); - fileStream.on('error', function(e) { - reject(e); - }); - tmpStream.pipe(res, {end: false}); - tmpStream.on('end', function() { - res.write(new Buffer('";', 'utf8')); - resolve(); - }); - tmpStream.on('error', function(e) { - reject(e); - }); - }); -} -function getObfuscateContent(res, fileStream, fileSize) { - return new Promise(function(resolve, reject) { - var tmpStream = fileStream.pipe(new OdttfProtocol(undefined, fileSize)); - fileStream.on('error', function(e) { - reject(e); - }); - tmpStream.pipe(res, {end: false}); - tmpStream.on('end', function() { - resolve(); - }); - tmpStream.on('error', function(e) { - reject(e); - }); - }); -} -function init() { - return co(function* () { - try { - if (cfgFontDir) { - yield* initFontMapByFolder(cfgFontDir, cfgSearchPatterns || '*.ttf;*.ttc;*.otf'); - } else { - logger.error('empty font dir'); - //initFontMapBySysFont(); - } - } catch (e) { - logger.error('error init:\r\n%s', e.stack); - } - }); -} -init(); -var zBase32Encoder = new ZBase32Encoder(); - -exports.getFont = function(req, res) { - return co(function* () { - try { - var startDate = null; - if(clientStatsD) { - startDate = new Date(); - } - logger.debug('Start getFont request'); - var fontname = req.params.fontname; - logger.debug('fontname:' + fontname); - - var fontExt = path.extname(fontname); - var fontnameDecoded; - if (JS_EXTENTION == fontExt) { - fontnameDecoded = zBase32Encoder.decode(path.basename(fontname, fontExt)); - } else { - fontnameDecoded = zBase32Encoder.decode(fontname); - } - logger.debug('fontnameDecoded:' + fontnameDecoded); - - var fontnameDecodedExt = path.extname(fontnameDecoded); - var realFontName; - if (JS_EXTENTION == fontnameDecodedExt) { - realFontName = path.basename(fontnameDecoded, fontnameDecodedExt); - } else { - realFontName = fontnameDecoded; - } - - var filePath = fontNameToFullPath[realFontName.toLowerCase()]; - if (filePath) { - var stats = yield utils.fsStat(filePath); - var lastModified = stats.mtime; - var eTag = lastModified.getTime().toString(16); - - var requestIfModSince = req.headers['if-modified-since']; - var requestETag = req.headers['if-none-match']; - if ((requestETag || requestIfModSince) && (!requestETag || requestETag == eTag) && - (!requestIfModSince || Math.abs(new Date(requestIfModSince).getTime() - lastModified.getTime()) < 1000)) { - res.sendStatus(304); - } else { - var expires = new Date(); - utils.addSeconds(expires, cfgResourceExpires); - res.set({ - 'Cache-Control': 'public', - 'Expires': expires.toUTCString(), - 'Content-Type': mime.lookup(fontnameDecoded), - 'Content-Disposition': utils.getContentDisposition(fontname, req.headers['user-agent']), - 'ETag': eTag, - 'Last-Modified': lastModified.toUTCString() - }); - var fileStream = yield utils.promiseCreateReadStream(filePath); - if (JS_EXTENTION == fontnameDecodedExt) { - yield getJsContent(res, fileStream, stats.size, realFontName); - } else { - yield getObfuscateContent(res, fileStream, stats.size); - } - res.end(); - } - } else { - res.sendStatus(404); - } - logger.debug('End getFont request'); - if(clientStatsD) { - clientStatsD.timing('coauth.getFont', new Date() - startDate); - } - } - catch (e) { - logger.error('error getFont:\r\n%s', e.stack); - res.sendStatus(400); - } - }); -}; diff --git a/DocService/sources/gc.js b/DocService/sources/gc.js index 960b9707c8..b647ff4902 100644 --- a/DocService/sources/gc.js +++ b/DocService/sources/gc.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,7 +32,7 @@ 'use strict'; -var config = require('config').get('services.CoAuthoring'); +const config = require('config'); var co = require('co'); var cron = require('cron'); var ms = require('ms'); @@ -44,49 +44,62 @@ var utils = require('./../../Common/sources/utils'); var logger = require('./../../Common/sources/logger'); var constants = require('./../../Common/sources/constants'); var commondefines = require('./../../Common/sources/commondefines'); -var pubsubRedis = require('./pubsubRedis.js'); var queueService = require('./../../Common/sources/taskqueueRabbitMQ'); -var pubsubService = require('./' + config.get('pubsub.name')); +var operationContext = require('./../../Common/sources/operationContext'); +var pubsubService = require('./pubsubRabbitMQ'); +const sqlBase = require("./databaseConnectors/baseConnector"); -var cfgRedisPrefix = config.get('redis.prefix'); -var cfgExpFilesCron = config.get('expire.filesCron'); -var cfgExpDocumentsCron = config.get('expire.documentsCron'); -var cfgExpFiles = config.get('expire.files'); -var cfgExpFilesRemovedAtOnce = config.get('expire.filesremovedatonce'); -var cfgForceSaveEnable = config.get('autoAssembly.enable'); -var cfgForceSaveStep = ms(config.get('autoAssembly.step')); +var cfgExpFilesCron = config.get('services.CoAuthoring.expire.filesCron'); +var cfgExpDocumentsCron = config.get('services.CoAuthoring.expire.documentsCron'); +var cfgExpFiles = config.get('services.CoAuthoring.expire.files'); +var cfgExpFilesRemovedAtOnce = config.get('services.CoAuthoring.expire.filesremovedatonce'); +var cfgForceSaveStep = ms(config.get('services.CoAuthoring.autoAssembly.step')); -var redisKeyDocuments = cfgRedisPrefix + constants.REDIS_KEY_DOCUMENTS; -var redisKeyForceSaveTimer = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE_TIMER; -var redisKeyForceSaveTimerLock = cfgRedisPrefix + constants.REDIS_KEY_FORCE_SAVE_TIMER_LOCK; +function getCronStep(cronTime){ + let cronJob = new cron.CronJob(cronTime, function(){}); + let dates = cronJob.nextDates(2); + return dates[1] - dates[0]; +} +let expFilesStep = getCronStep(cfgExpFilesCron); +let expDocumentsStep = getCronStep(cfgExpDocumentsCron); -var checkFileExpire = function() { +var checkFileExpire = function(expireSeconds) { return co(function* () { + let ctx = new operationContext.Context(); try { - logger.debug('checkFileExpire start'); + ctx.logger.info('checkFileExpire start'); + let removedCount = 0; var expired; - var removedCount = 0; var currentRemovedCount; do { currentRemovedCount = 0; - expired = yield taskResult.getExpired(cfgExpFilesRemovedAtOnce, cfgExpFiles); + expired = yield taskResult.getExpired(ctx, cfgExpFilesRemovedAtOnce, expireSeconds ?? cfgExpFiles); for (var i = 0; i < expired.length; ++i) { - var docId = expired[i].id; - //проверÑем что никто не Ñидит в документе - var hvals = yield docsCoServer.getAllPresencePromise(docId); - if(0 == hvals.length){ - if (yield canvasService.cleanupCache(docId)) { + let tenant = expired[i].tenant; + let docId = expired[i].id; + let shardKey = sqlBase.DocumentAdditional.prototype.getShardKey(expired[i].additional); + let wopiSrc = sqlBase.DocumentAdditional.prototype.getWopiSrc(expired[i].additional); + ctx.init(tenant, docId, ctx.userId, shardKey, wopiSrc); + yield ctx.initTenantCache(); + //todo tenant + //check that no one is in the document + let editorsCount = yield docsCoServer.getEditorsCountPromise(ctx, docId); + if(0 === editorsCount){ + if (yield canvasService.cleanupCache(ctx, docId)) { currentRemovedCount++; } } else { - logger.debug('checkFileExpire expire but presence: hvals = %s; docId = %s', hvals, docId); + ctx.logger.debug('checkFileExpire expire but presence: editorsCount = %d', editorsCount); } } removedCount += currentRemovedCount; } while (currentRemovedCount > 0); - logger.debug('checkFileExpire end: removedCount = %d', removedCount); + ctx.initDefault(); + ctx.logger.info('checkFileExpire end: removedCount = %d', removedCount); } catch (e) { - logger.error('checkFileExpire error:\r\n%s', e.stack); + ctx.logger.error('checkFileExpire error: %s', e.stack); + } finally { + setTimeout(checkFileExpire, expFilesStep); } }); }; @@ -95,46 +108,46 @@ var checkDocumentExpire = function() { var queue = null; var removedCount = 0; var startSaveCount = 0; + let ctx = new operationContext.Context(); try { - logger.debug('checkDocumentExpire start'); - var redisClient = pubsubRedis.getClientRedis(); - + ctx.logger.info('checkDocumentExpire start'); var now = (new Date()).getTime(); - var multi = redisClient.multi([ - ['zrangebyscore', redisKeyDocuments, 0, now], - ['zremrangebyscore', redisKeyDocuments, 0, now] - ]); - var execRes = yield utils.promiseRedis(multi, multi.exec); - var expiredKeys = execRes[0]; + let expiredKeys = yield docsCoServer.editorData.getDocumentPresenceExpired(now); if (expiredKeys.length > 0) { queue = new queueService(); - yield queue.initPromise(true, false, false, false); + yield queue.initPromise(true, false, false, false, false, false); for (var i = 0; i < expiredKeys.length; ++i) { - var docId = expiredKeys[i]; + let tenant = expiredKeys[i][0]; + let docId = expiredKeys[i][1]; if (docId) { - var puckerIndex = yield docsCoServer.getChangesIndexPromise(docId); - if (puckerIndex > 0) { - yield docsCoServer.createSaveTimerPromise(docId, null, queue, true); + ctx.init(tenant, docId, ctx.userId); + yield ctx.initTenantCache(); + var hasChanges = yield docsCoServer.hasChanges(ctx, docId); + if (hasChanges) { + //todo opt_initShardKey from getDocumentPresenceExpired data or from db + yield docsCoServer.createSaveTimer(ctx, docId, null, null, null, queue, true, true); startSaveCount++; } else { - yield docsCoServer.cleanDocumentOnExitNoChangesPromise(docId); + yield docsCoServer.cleanDocumentOnExitNoChangesPromise(ctx, docId); removedCount++; } } } } + ctx.initDefault(); + ctx.logger.info('checkDocumentExpire end: startSaveCount = %d, removedCount = %d', startSaveCount, removedCount); } catch (e) { - logger.error('checkDocumentExpire error:\r\n%s', e.stack); + ctx.logger.error('checkDocumentExpire error: %s', e.stack); } finally { try { if (queue) { yield queue.close(); } } catch (e) { - logger.error('checkDocumentExpire error:\r\n%s', e.stack); + ctx.logger.error('checkDocumentExpire error: %s', e.stack); } - logger.debug('checkDocumentExpire end: startSaveCount = %d, removedCount = %d', startSaveCount, removedCount); + setTimeout(checkDocumentExpire, expDocumentsStep); } }); }; @@ -142,39 +155,38 @@ let forceSaveTimeout = function() { return co(function* () { let queue = null; let pubsub = null; + let ctx = new operationContext.Context(); try { - logger.debug('forceSaveTimeout start'); - let redisClient = pubsubRedis.getClientRedis(); - + ctx.logger.info('forceSaveTimeout start'); let now = (new Date()).getTime(); - let multi = redisClient.multi([ - ['zrangebyscore', redisKeyForceSaveTimer, 0, now], - ['zremrangebyscore', redisKeyForceSaveTimer, 0, now] - ]); - let execRes = yield utils.promiseRedis(multi, multi.exec); - let expiredKeys = execRes[0]; + let expiredKeys = yield docsCoServer.editorData.getForceSaveTimer(now); if (expiredKeys.length > 0) { queue = new queueService(); - yield queue.initPromise(true, false, false, false); + yield queue.initPromise(true, false, false, false, false, false); pubsub = new pubsubService(); yield pubsub.initPromise(); let actions = []; for (let i = 0; i < expiredKeys.length; ++i) { - let docId = expiredKeys[i]; + let tenant = expiredKeys[i][0]; + let docId = expiredKeys[i][1]; if (docId) { - actions.push(utils.promiseRedis(redisClient, redisClient.del, redisKeyForceSaveTimerLock + docId)); - actions.push(docsCoServer.startForceSavePromise(docId, commondefines.c_oAscForceSaveTypes.Timeout, - undefined, undefined, undefined, queue, pubsub)); + ctx.init(tenant, docId, ctx.userId); + yield ctx.initTenantCache(); + //todo opt_initShardKey from ForceSave data or from db + actions.push(docsCoServer.startForceSave(ctx, docId, commondefines.c_oAscForceSaveTypes.Timeout, + undefined, undefined, undefined, undefined, + undefined, undefined, undefined, undefined, queue, pubsub, undefined, true)); } } yield Promise.all(actions); - logger.debug('forceSaveTimeout actions.length %d', actions.length); + ctx.logger.debug('forceSaveTimeout actions.length %d', actions.length); } - logger.debug('forceSaveTimeout end'); + ctx.initDefault(); + ctx.logger.info('forceSaveTimeout end'); } catch (e) { - logger.error('forceSaveTimeout error:\r\n%s', e.stack); + ctx.logger.error('forceSaveTimeout error: %s', e.stack); } finally { try { if (queue) { @@ -184,29 +196,17 @@ let forceSaveTimeout = function() { yield pubsub.close(); } } catch (e) { - logger.error('checkDocumentExpire error:\r\n%s', e.stack); + ctx.logger.error('checkDocumentExpire error: %s', e.stack); } setTimeout(forceSaveTimeout, cfgForceSaveStep); } }); }; -var documentExpireJob = function(opt_isStart) { - if (!opt_isStart) { - logger.warn('checkDocumentExpire restart'); - } - new cron.CronJob(cfgExpDocumentsCron, checkDocumentExpire, documentExpireJob, true); -}; -documentExpireJob(true); - -var fileExpireJob = function(opt_isStart) { - if (!opt_isStart) { - logger.warn('checkFileExpire restart'); - } - new cron.CronJob(cfgExpFilesCron, checkFileExpire, fileExpireJob, true); -}; -fileExpireJob(true); - -if (cfgForceSaveEnable) { +exports.startGC = function() { + setTimeout(checkDocumentExpire, expDocumentsStep); + setTimeout(checkFileExpire, expFilesStep); setTimeout(forceSaveTimeout, cfgForceSaveStep); -} +}; +exports.getCronStep = getCronStep; +exports.checkFileExpire = checkFileExpire; diff --git a/DocService/sources/mySqlBaseConnector.js b/DocService/sources/mySqlBaseConnector.js deleted file mode 100644 index 80313e1d8a..0000000000 --- a/DocService/sources/mySqlBaseConnector.js +++ /dev/null @@ -1,104 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * Pursuant to Section 7(b) of the License you must retain the original Product - * logo when distributing the program. Pursuant to Section 7(e) we decline to - * grant you any rights under trademark law for use of our trademarks. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing 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 - * - */ - -'use strict'; - -var mysql = require('mysql'); -var sqlBase = require('./baseConnector'); -var configSql = require('config').get('services.CoAuthoring.sql'); -var pool = mysql.createPool({ - host : configSql.get('dbHost'), - port : configSql.get('dbPort'), - user : configSql.get('dbUser'), - password : configSql.get('dbPass'), - database : configSql.get('dbName'), - charset : configSql.get('charset'), - connectionLimit : configSql.get('connectionlimit'), - timezone : '+0000', - flags : '-FOUND_ROWS' -}); -var logger = require('./../../Common/sources/logger'); - -exports.sqlQuery = function (sqlCommand, callbackFunction) { - pool.getConnection(function(err, connection) { - if (err) { - logger.error('pool.getConnection error: %s', err); - if (callbackFunction) callbackFunction(err, null); - return; - } - - connection.query(sqlCommand, function (error, result) { - connection.release(); - if (error) { - logger.error('________________________error_____________________'); - logger.error('sqlQuery: %s sqlCommand: %s', error.code, sqlCommand); - logger.error(error); - logger.error('_____________________end_error_____________________'); - } - if (callbackFunction) callbackFunction(error, result); - }); - }); -}; -exports.sqlEscape = function (value) { - return pool.escape(value); -}; - -function getUpsertString(task, opt_updateUserIndex) { - task.completeDefaults(); - var dateNow = sqlBase.getDateTime(new Date()); - var commandArg = [task.key, task.status, task.statusInfo, dateNow, task.userIndex, task.changeId, task.callback, task.baseurl]; - var commandArgEsc = commandArg.map(function(curVal) { - return exports.sqlEscape(curVal) - }); - var sql = 'INSERT INTO task_result ( id, status, status_info, last_open_date,' + - ' user_index, change_id, callback, baseurl ) VALUES (' + commandArgEsc.join(', ') + ') ON DUPLICATE KEY UPDATE' + - ' last_open_date = ' + exports.sqlEscape(dateNow); - if (opt_updateUserIndex) { - sql += ', user_index = LAST_INSERT_ID(user_index + 1);'; - } else { - sql += ';'; - } - return sql; -} - -exports.upsert = function(task, opt_updateUserIndex) { - return new Promise(function(resolve, reject) { - var sqlCommand = getUpsertString(task, opt_updateUserIndex); - exports.sqlQuery(sqlCommand, function(error, result) { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - }); -}; diff --git a/DocService/sources/postgreSqlBaseConnector.js b/DocService/sources/postgreSqlBaseConnector.js deleted file mode 100644 index 6afe483006..0000000000 --- a/DocService/sources/postgreSqlBaseConnector.js +++ /dev/null @@ -1,157 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * Pursuant to Section 7(b) of the License you must retain the original Product - * logo when distributing the program. Pursuant to Section 7(e) we decline to - * grant you any rights under trademark law for use of our trademarks. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing 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 - * - */ - -'use strict'; - -var pg = require('pg'); -var co = require('co'); -var pgEscape = require('pg-escape'); -var types = require('pg').types; -var sqlBase = require('./baseConnector'); -var configSql = require('config').get('services.CoAuthoring.sql'); -var pool = new pg.Pool({ - host: configSql.get('dbHost'), - port: configSql.get('dbPort'), - user: configSql.get('dbUser'), - password: configSql.get('dbPass'), - database: configSql.get('dbName'), - max: configSql.get('connectionlimit'), - min: 0, - ssl: false, - idleTimeoutMillis: 30000 -}); -//todo datetime timezone -types.setTypeParser(1114, function(stringValue) { - return new Date(stringValue + '+0000'); -}); -types.setTypeParser(1184, function(stringValue) { - return new Date(stringValue + '+0000'); -}); - -var logger = require('./../../Common/sources/logger'); - -exports.sqlQuery = function(sqlCommand, callbackFunction, opt_noModifyRes, opt_noLog) { - co(function *() { - var client = null; - var result = null; - var error = null; - try { - client = yield pool.connect(); - result = yield client.query(sqlCommand); - } catch (err) { - error = err; - if (!opt_noLog) { - if (client) { - logger.error('sqlQuery error sqlCommand: %s:\r\n%s', sqlCommand.slice(0, 50), err.stack); - } else { - logger.error('pool.getConnection error: %s', err); - } - } - } finally { - if (client) { - client.release(); - } - if (callbackFunction) { - var output = result; - if (result && !opt_noModifyRes) { - if ('SELECT' === result.command) { - output = result.rows; - } else { - output = {affectedRows: result.rowCount}; - } - } - callbackFunction(error, output); - } - } - }); -}; -exports.sqlEscape = function(value) { - //todo parameterized queries - return undefined !== value ? pgEscape.literal(value.toString()) : 'NULL'; -}; -var isSupportOnConflict = false; -(function checkIsSupportOnConflict() { - var sqlCommand = 'INSERT INTO checkIsSupportOnConflict (id) VALUES(1) ON CONFLICT DO NOTHING;'; - exports.sqlQuery(sqlCommand, function(error, result) { - if (error) { - if ('42601' == error.code) { - //SYNTAX ERROR - isSupportOnConflict = false; - logger.debug('checkIsSupportOnConflict false'); - } else if ('42P01' == error.code) { - // UNDEFINED TABLE - isSupportOnConflict = true; - logger.debug('checkIsSupportOnConflict true'); - } else { - logger.error('checkIsSupportOnConflict unexpected error code:\r\n%s', error.stack); - } - } - }, true, true); -})(); - -function getUpsertString(task) { - task.completeDefaults(); - var dateNow = sqlBase.getDateTime(new Date()); - var commandArg = [task.key, task.status, task.statusInfo, dateNow, task.userIndex, task.changeId, task.callback, task.baseurl]; - var commandArgEsc = commandArg.map(function(curVal) { - return exports.sqlEscape(curVal) - }); - if (isSupportOnConflict) { - //http://stackoverflow.com/questions/34762732/how-to-find-out-if-an-upsert-was-an-update-with-postgresql-9-5-upsert - return "INSERT INTO task_result (id, status, status_info, last_open_date, user_index, change_id, callback," + - " baseurl) SELECT " + commandArgEsc.join(', ') + " WHERE 'false' = set_config('myapp.isupdate', 'false', true) " + - "ON CONFLICT (id) DO UPDATE SET last_open_date = " + - sqlBase.baseConnector.sqlEscape(dateNow) + - ", user_index = task_result.user_index + 1 WHERE 'true' = set_config('myapp.isupdate', 'true', true) RETURNING" + - " current_setting('myapp.isupdate') as isupdate, user_index as userindex;"; - } else { - return "SELECT * FROM merge_db(" + commandArgEsc.join(', ') + ");"; - } -} -exports.upsert = function(task) { - return new Promise(function(resolve, reject) { - var sqlCommand = getUpsertString(task); - exports.sqlQuery(sqlCommand, function(error, result) { - if (error) { - reject(error); - } else { - if (result && result.rows.length > 0) { - var first = result.rows[0]; - result = {affectedRows: 0, insertId: 0}; - result.affectedRows = 'true' == first.isupdate ? 2 : 1; - result.insertId = first.userindex; - } - resolve(result); - } - }, true); - }); -}; diff --git a/DocService/sources/pubsubRabbitMQ.js b/DocService/sources/pubsubRabbitMQ.js index e25e394c91..dcb33c034b 100644 --- a/DocService/sources/pubsubRabbitMQ.js +++ b/DocService/sources/pubsubRabbitMQ.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -31,32 +31,41 @@ */ 'use strict'; +var config = require('config'); var events = require('events'); var util = require('util'); var co = require('co'); +var constants = require('./../../Common/sources/constants'); +const commonDefines = require('./../../Common/sources/commondefines'); var utils = require('./../../Common/sources/utils'); var rabbitMQCore = require('./../../Common/sources/rabbitMQCore'); +var activeMQCore = require('./../../Common/sources/activeMQCore'); -var cfgRabbitExchangePubSub = require('config').get('rabbitmq.exchangepubsub'); +const cfgQueueType = config.get('queue.type'); +const cfgRabbitExchangePubSub = config.get('rabbitmq.exchangepubsub'); +const cfgRabbitQueuePubsub = config.get('rabbitmq.queuepubsub'); +var cfgActiveTopicPubSub = constants.ACTIVEMQ_TOPIC_PREFIX + config.get('activemq.topicpubsub'); -function init(pubsub, callback) { +function initRabbit(pubsub, callback) { return co(function* () { var e = null; try { var conn = yield rabbitMQCore.connetPromise(function() { clear(pubsub); if (!pubsub.isClose) { - init(pubsub, null); + setTimeout(() => { + init(pubsub, null); + }, rabbitMQCore.RECONNECT_TIMEOUT); } }); pubsub.connection = conn; pubsub.channelPublish = yield rabbitMQCore.createChannelPromise(conn); - pubsub.exchangePublish = yield rabbitMQCore.assertExchangePromise(pubsub.channelPublish, cfgRabbitExchangePubSub, - 'fanout', {durable: true}); + pubsub.exchangePublish = yield rabbitMQCore.assertExchangePromise(pubsub.channelPublish, cfgRabbitExchangePubSub.name, + 'fanout', cfgRabbitExchangePubSub.options); pubsub.channelReceive = yield rabbitMQCore.createChannelPromise(conn); - var queue = yield rabbitMQCore.assertQueuePromise(pubsub.channelReceive, '', {autoDelete: true, exclusive: true}); - pubsub.channelReceive.bindQueue(queue, cfgRabbitExchangePubSub, ''); + var queue = yield rabbitMQCore.assertQueuePromise(pubsub.channelReceive, cfgRabbitQueuePubsub.name, cfgRabbitQueuePubsub.options); + pubsub.channelReceive.bindQueue(queue, cfgRabbitExchangePubSub.name, ''); yield rabbitMQCore.consumePromise(pubsub.channelReceive, queue, function (message) { if(null != pubsub.channelReceive){ if (message) { @@ -66,7 +75,58 @@ function init(pubsub, callback) { } }, {noAck: false}); //process messages received while reconnection time - repeat(pubsub); + yield repeat(pubsub); + } catch (err) { + e = err; + } + if (callback) { + callback(e); + } + }); +} +function initActive(pubsub, callback) { + return co(function*() { + var e = null; + try { + var conn = yield activeMQCore.connetPromise(function() { + clear(pubsub); + if (!pubsub.isClose) { + setTimeout(() => { + init(pubsub, null); + }, activeMQCore.RECONNECT_TIMEOUT); + } + }); + pubsub.connection = conn; + //https://github.com/amqp/rhea/issues/251#issuecomment-535076570 + let optionsPubSubSender = { + target: { + address: cfgActiveTopicPubSub, + capabilities: ['topic'] + } + }; + pubsub.channelPublish = yield activeMQCore.openSenderPromise(conn, optionsPubSubSender); + + let optionsPubSubReceiver = { + source: { + address: cfgActiveTopicPubSub, + capabilities: ['topic'] + }, + credit_window: 0, + autoaccept: false + }; + let receiver = yield activeMQCore.openReceiverPromise(conn, optionsPubSubReceiver); + //todo ?consumer.dispatchAsync=false&consumer.prefetchSize=1 + receiver.add_credit(1); + receiver.on("message", function(context) { + if (context) { + pubsub.emit('message', context.message.body); + } + + context.delivery.accept(); + receiver.add_credit(1); + }); + //process messages received while reconnection time + yield repeat(pubsub); } catch (err) { e = err; } @@ -81,13 +141,82 @@ function clear(pubsub) { pubsub.channelReceive = null; } function repeat(pubsub) { - for (var i = 0; i < pubsub.publishStore.length; ++i) { - publish(pubsub, pubsub.publishStore[i]); - } - pubsub.publishStore.length = 0; + return co(function*() { + for (var i = 0; i < pubsub.publishStore.length; ++i) { + yield publish(pubsub, pubsub.publishStore[i]); + } + pubsub.publishStore.length = 0; + }); + } -function publish(pubsub, data) { - pubsub.channelPublish.publish(pubsub.exchangePublish, '', data); +function publishRabbit(pubsub, data) { + return new Promise(function (resolve, reject) { + //Channels act like stream.Writable when you call publish or sendToQueue: they return either true, meaning “keep sendingâ€, or false, meaning “please wait for a ‘drain’ eventâ€. + let keepSending = pubsub.channelPublish.publish(pubsub.exchangePublish, '', data); + if (!keepSending) { + //todo (node:4308) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 drain listeners added to [Sender]. Use emitter.setMaxListeners() to increase limit + pubsub.channelPublish.once('drain', resolve); + } else { + resolve(); + } + }); +} + +function publishActive(pubsub, data) { + return new Promise(function (resolve, reject) { + //Returns true if the sender has available credits for sending a message. Otherwise it returns false. + let sendable = pubsub.channelPublish.sendable(); + if (!sendable) { + //todo (node:4308) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 sendable listeners added to [Sender]. Use emitter.setMaxListeners() to increase limit + pubsub.channelPublish.once('sendable', () => { + resolve(publishActive(pubsub, data)); + }); + } else { + pubsub.channelPublish.send({durable: true, body: data}); + resolve(); + } + }); +} +function closeRabbit(conn) { + return rabbitMQCore.closePromise(conn); +} +function closeActive(conn) { + return activeMQCore.closePromise(conn); +} + +function healthCheckRabbit(pubsub) { + return co(function* () { + if (!pubsub.channelPublish) { + return false; + } + const exchange = yield rabbitMQCore.assertExchangePromise(pubsub.channelPublish, cfgRabbitExchangePubSub.name, + 'fanout', cfgRabbitExchangePubSub.options); + return !!exchange; + }); +} +function healthCheckActive(pubsub) { + return co(function* () { + if (!pubsub.connection) { + return false; + } + return pubsub.connection.is_open(); + }); +} + +let init; +let publish; +let close; +let healthCheck; +if (commonDefines.c_oAscQueueType.rabbitmq === cfgQueueType) { + init = initRabbit; + publish = publishRabbit; + close = closeRabbit; + healthCheck = healthCheckRabbit; +} else { + init = initActive; + publish = publishActive; + close = closeActive; + healthCheck = healthCheckActive; } function PubsubRabbitMQ() { @@ -115,25 +244,20 @@ PubsubRabbitMQ.prototype.initPromise = function() { }); }; PubsubRabbitMQ.prototype.publish = function (message) { - var data = new Buffer(message); + const data = Buffer.from(message); if (null != this.channelPublish) { - publish(this, data); + return publish(this, data); } else { this.publishStore.push(data); + return Promise.resolve(); } }; PubsubRabbitMQ.prototype.close = function() { - var t = this; this.isClose = true; - return new Promise(function(resolve, reject) { - t.connection.close(function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); + return close(this.connection); +}; +PubsubRabbitMQ.prototype.healthCheck = function() { + return healthCheck(this); }; module.exports = PubsubRabbitMQ; diff --git a/DocService/sources/pubsubRedis.js b/DocService/sources/pubsubRedis.js deleted file mode 100644 index 58e63d2832..0000000000 --- a/DocService/sources/pubsubRedis.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 - * - * This program is a free software product. You can redistribute it and/or - * modify it under the terms of the GNU Affero General Public License (AGPL) - * version 3 as published by the Free Software Foundation. In accordance with - * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect - * that Ascensio System SIA expressly excludes the warranty of non-infringement - * of any third-party rights. - * - * This program is distributed WITHOUT ANY WARRANTY; without even the implied - * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For - * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html - * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. - * - * The interactive user interfaces in modified source and object code versions - * of the Program must display Appropriate Legal Notices, as required under - * Section 5 of the GNU AGPL version 3. - * - * Pursuant to Section 7(b) of the License you must retain the original Product - * logo when distributing the program. Pursuant to Section 7(e) we decline to - * grant you any rights under trademark law for use of our trademarks. - * - * All the Product's GUI elements, including illustrations and icon sets, as - * well as technical writing 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 - * - */ - -'use strict'; -var config = require('config').get('services.CoAuthoring.redis'); -var events = require('events'); -var util = require('util'); -var logger = require('./../../Common/sources/logger'); -var constants = require('./../../Common/sources/constants'); -var redis = require(config.get('name')); - -var cfgRedisPrefix = config.get('prefix'); -var cfgRedisHost = config.get('host'); -var cfgRedisPort = config.get('port'); - -var channelName = cfgRedisPrefix + constants.REDIS_KEY_PUBSUB; - -function createClientRedis() { - var redisClient = redis.createClient(cfgRedisPort, cfgRedisHost, {}); - redisClient.on('error', function(err) { - logger.error('redisClient error %s', err.toString()); - }); - return redisClient; -} -var g_redisClient = null; -function getClientRedis() { - if (!g_redisClient) { - g_redisClient = createClientRedis(); - } - return g_redisClient; -} - -function PubsubRedis() { - this.clientPublish = null; - this.clientSubscribe = null; -} -util.inherits(PubsubRedis, events.EventEmitter); -PubsubRedis.prototype.init = function(callback) { - var pubsub = this; - pubsub.clientPublish = createClientRedis(); - pubsub.clientSubscribe = createClientRedis(); - pubsub.clientSubscribe.subscribe(channelName); - pubsub.clientSubscribe.on('message', function(channel, message) { - pubsub.emit('message', message); - }); - callback(null); -}; -PubsubRedis.prototype.initPromise = function() { - var t = this; - return new Promise(function(resolve, reject) { - t.init(function(err) { - if (err) { - reject(err); - } else { - resolve(); - } - }); - }); -}; -PubsubRedis.prototype.publish = function(data) { - this.clientPublish.publish(channelName, data); -}; -PubsubRedis.prototype.close = function() { - var t = this; - return new Promise(function(resolve, reject) { - t.clientPublish.quit(); - t.clientSubscribe.quit(); - resolve(); - }); -}; - -module.exports = PubsubRedis; -module.exports.getClientRedis = getClientRedis; diff --git a/DocService/sources/routes/static.js b/DocService/sources/routes/static.js new file mode 100644 index 0000000000..6051ae2c0a --- /dev/null +++ b/DocService/sources/routes/static.js @@ -0,0 +1,104 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2023 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; +const express = require('express'); +const config = require("config"); +const operationContext = require('./../../../Common/sources/operationContext'); +const utils = require('./../../../Common/sources/utils'); +const storage = require('./../../../Common/sources/storage-base'); +const urlModule = require("url"); +const path = require("path"); +const mime = require("mime"); + +const cfgStaticContent = config.has('services.CoAuthoring.server.static_content') ? config.get('services.CoAuthoring.server.static_content') : {}; +const cfgCacheStorage = config.get('storage'); +const cfgPersistentStorage = utils.deepMergeObjects({}, cfgCacheStorage, config.get('persistentStorage')); +const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles'); +const cfgErrorFiles = config.get('FileConverter.converter.errorfiles'); + +const router = express.Router(); + +function initCacheRouter(cfgStorage, routs) { + const bucketName = cfgStorage.bucketName; + const storageFolderName = cfgStorage.storageFolderName; + const folderPath = cfgStorage.fs.folderPath; + routs.forEach((rout) => { + //special dirs are empty by default + if (!rout) { + return; + } + let rootPath = path.join(folderPath, rout); + router.use(`/${bucketName}/${storageFolderName}/${rout}`, (req, res, next) => { + const index = req.url.lastIndexOf('/'); + if ('GET' === req.method && index > 0) { + let sendFileOptions = { + root: rootPath, dotfiles: 'deny', headers: { + 'Content-Disposition': 'attachment' + } + }; + const urlParsed = urlModule.parse(req.url); + if (urlParsed && urlParsed.pathname) { + const filename = decodeURIComponent(path.basename(urlParsed.pathname)); + sendFileOptions.headers['Content-Type'] = mime.getType(filename); + } + const realUrl = decodeURI(req.url.substring(0, index)); + res.sendFile(realUrl, sendFileOptions, (err) => { + if (err) { + operationContext.global.logger.error(err); + res.status(400).end(); + } + }); + } else { + res.sendStatus(404); + } + }); + }); +} + +for (let i in cfgStaticContent) { + if (cfgStaticContent.hasOwnProperty(i)) { + router.use(i, express.static(cfgStaticContent[i]['path'], cfgStaticContent[i]['options'])); + } +} +if (storage.needServeStatic()) { + initCacheRouter(cfgCacheStorage, [cfgCacheStorage.cacheFolderName]); +} +if (storage.needServeStatic(cfgForgottenFiles)) { + let persistentRouts = [cfgForgottenFiles, cfgErrorFiles]; + persistentRouts.filter((rout) => {return rout && rout.length > 0;}); + if (persistentRouts.length > 0) { + initCacheRouter(cfgPersistentStorage, [cfgForgottenFiles, cfgErrorFiles]); + } +} + +module.exports = router; diff --git a/DocService/sources/server.js b/DocService/sources/server.js index 707a392a05..150dca5086 100644 --- a/DocService/sources/server.js +++ b/DocService/sources/server.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,268 +32,381 @@ 'use strict'; -const cluster = require('cluster'); -const configCommon = require('config'); -const config = configCommon.get('services.CoAuthoring'); -//process.env.NODE_ENV = config.get('server.mode'); +const config = require('config'); +//process.env.NODE_ENV = config.get('services.CoAuthoring.server.mode'); const logger = require('./../../Common/sources/logger'); const co = require('co'); +const license = require('./../../Common/sources/license'); +const fs = require('fs'); -if (cluster.isMaster) { - const fs = require('fs'); +const express = require('express'); +const http = require('http'); +const urlModule = require('url'); +const path = require('path'); +const bodyParser = require("body-parser"); +const multer = require('multer'); +const mime = require('mime'); +const apicache = require('apicache'); +const docsCoServer = require('./DocsCoServer'); +const canvasService = require('./canvasservice'); +const converterService = require('./converterservice'); +const fileUploaderService = require('./fileuploaderservice'); +const wopiClient = require('./wopiClient'); +const constants = require('./../../Common/sources/constants'); +const utils = require('./../../Common/sources/utils'); +const commonDefines = require('./../../Common/sources/commondefines'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); +const staticRouter = require('./routes/static'); +const ms = require('ms'); - //const numCPUs = require('os').cpus().length; - const license = require('./../../Common/sources/license'); +const cfgWopiEnable = config.get('wopi.enable'); +const cfgWopiDummyEnable = config.get('wopi.dummy.enable'); +const cfgHtmlTemplate = config.get('wopi.htmlTemplate'); +const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); +const cfgTokenEnableRequestInbox = config.get('services.CoAuthoring.token.enable.request.inbox'); +const cfgTokenEnableRequestOutbox = config.get('services.CoAuthoring.token.enable.request.outbox'); +const cfgLicenseFile = config.get('license.license_file'); +const cfgDownloadMaxBytes = config.get('FileConverter.converter.maxDownloadBytes'); - //const cfgWorkerPerCpu = config.get('server.workerpercpu'); - let licenseInfo, workersCount = 0, updateTime; - const readLicense = function*() { - licenseInfo = yield* license.readLicense(); - workersCount = Math.min(1, licenseInfo.count/*, Math.ceil(numCPUs * cfgWorkerPerCpu)*/); - }; - const updateLicenseWorker = (worker) => { - worker.send({type: 1, data: licenseInfo}); - }; - const updateWorkers = () => { - const arrKeyWorkers = Object.keys(cluster.workers); - if (arrKeyWorkers.length < workersCount) { - for (let i = arrKeyWorkers.length; i < workersCount; ++i) { - const newWorker = cluster.fork(); - logger.warn('worker %s started.', newWorker.process.pid); - } - } else { - for (let i = workersCount; i < arrKeyWorkers.length; ++i) { - const killWorker = cluster.workers[arrKeyWorkers[i]]; - if (killWorker) { - killWorker.kill(); - } - } +if (false) { + var cluster = require('cluster'); + cluster.schedulingPolicy = cluster.SCHED_RR + if (cluster.isMaster) { + let workersCount = 2; + logger.warn('start cluster with %s workers %s', workersCount, cluster.schedulingPolicy); + for (let nIndexWorker = 0; nIndexWorker < workersCount; ++nIndexWorker) { + var worker = cluster.fork().process; + logger.warn('worker %s started.', worker.pid); } - }; - const updatePlugins = (eventType, filename) => { - console.log('update Folder: %s ; %s', eventType, filename); - if (updateTime && 1000 >= (new Date() - updateTime)) { - return; - } - console.log('update Folder true: %s ; %s', eventType, filename); - updateTime = new Date(); - for (let i in cluster.workers) { - cluster.workers[i].send({type: 2}); - } - }; - const updateLicense = () => { - return co(function*() { - try { - yield* readLicense(); - logger.warn('update cluster with %s workers', workersCount); - for (let i in cluster.workers) { - updateLicenseWorker(cluster.workers[i]); - } - updateWorkers(); - } catch (err) { - logger.error('updateLicense error:\r\n%s', err.stack); - } + + cluster.on('exit', function (worker) { + logger.warn('worker %s died. restart...', worker.process.pid); + cluster.fork(); }); - }; + return; + } +} - cluster.on('fork', (worker) => { - updateLicenseWorker(worker); - }); - cluster.on('exit', (worker, code, signal) => { - logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal); - updateWorkers(); - }); +const app = express(); +app.disable('x-powered-by'); +//path.resolve uses __dirname by default(unexpected path in pkg) +app.set("views", path.resolve(process.cwd(), cfgHtmlTemplate)); +app.set("view engine", "ejs"); +const server = http.createServer(app); - updateLicense(); +let licenseInfo, licenseOriginal, updatePluginsTime, userPlugins; +const updatePluginsCacheExpire = ms("5m"); +const updatePlugins = (eventType, filename) => { + operationContext.global.logger.info('update Folder true: %s ; %s', eventType, filename); + userPlugins = undefined; +}; +const readLicense = async function () { + [licenseInfo, licenseOriginal] = await license.readLicense(cfgLicenseFile); +}; +const updateLicense = async () => { try { - fs.watch(config.get('plugins.path'), updatePlugins); - } catch (e) { - logger.warn('Plugins watch exception (https://nodejs.org/docs/latest/api/fs.html#fs_availability).'); + await readLicense(); + await docsCoServer.setLicenseInfo(operationContext.global, licenseInfo, licenseOriginal); + operationContext.global.logger.info('End updateLicense'); + } catch (err) { + operationContext.global.logger.error('updateLicense error: %s', err.stack); } - fs.watchFile(configCommon.get('license').get('license_file'), updateLicense); - setInterval(updateLicense, 86400000); -} else { - logger.warn('Express server starting...'); - - const express = require('express'); - const http = require('http'); - const urlModule = require('url'); - const path = require('path'); - const bodyParser = require("body-parser"); - const mime = require('mime'); - const docsCoServer = require('./DocsCoServer'); - const canvasService = require('./canvasservice'); - const converterService = require('./converterservice'); - const fontService = require('./fontservice'); - const fileUploaderService = require('./fileuploaderservice'); - const constants = require('./../../Common/sources/constants'); - const utils = require('./../../Common/sources/utils'); - const commonDefines = require('./../../Common/sources/commondefines'); - const configStorage = configCommon.get('storage'); - const app = express(); - const server = http.createServer(app); - - let userPlugins = null, updatePlugins = true; - - if (config.has('server.static_content')) { - const staticContent = config.get('server.static_content'); - for (let i in staticContent) { - app.use(i, express.static(staticContent[i]['path'], staticContent[i]['options'])); - } +}; + +operationContext.global.logger.warn('Express server starting...'); + +if (!(cfgTokenEnableBrowser && cfgTokenEnableRequestInbox && cfgTokenEnableRequestOutbox)) { + operationContext.global.logger.warn('Set services.CoAuthoring.token.enable.browser, services.CoAuthoring.token.enable.request.inbox, ' + + 'services.CoAuthoring.token.enable.request.outbox in the Document Server config ' + + 'to prevent an unauthorized access to your documents and the substitution of important parameters in Document Server requests.'); +} + +updateLicense(); +fs.watchFile(cfgLicenseFile, updateLicense); +setInterval(updateLicense, 86400000); + +try { + let staticContent = config.get('services.CoAuthoring.server.static_content'); + let pluginsUri = config.get('services.CoAuthoring.plugins.uri'); + let pluginsPath = undefined; + if (staticContent[pluginsUri]) { + pluginsPath = staticContent[pluginsUri].path; } + fs.watch(pluginsPath, updatePlugins); +} catch (e) { + operationContext.global.logger.warn('Failed to subscribe to plugin folder updates. When changing the list of plugins, you must restart the server. https://nodejs.org/docs/latest/api/fs.html#fs_availability. %s', e.stack); +} - if (configStorage.has('fs.folderPath')) { - const cfgBucketName = configStorage.get('bucketName'); - const cfgStorageFolderName = configStorage.get('storageFolderName'); - app.use('/' + cfgBucketName + '/' + cfgStorageFolderName, (req, res, next) => { - const index = req.url.lastIndexOf('/'); - if ('GET' === req.method && -1 != index) { - const contentDisposition = req.query['disposition'] || 'attachment'; - let sendFileOptions = { - root: configStorage.get('fs.folderPath'), dotfiles: 'deny', headers: { - 'Content-Disposition': contentDisposition - } - }; - const urlParsed = urlModule.parse(req.url); - if (urlParsed && urlParsed.pathname) { - const filename = decodeURIComponent(path.basename(urlParsed.pathname)); - sendFileOptions.headers['Content-Type'] = mime.lookup(filename); +// If you want to use 'development' and 'production', +// then with app.settings.env (https://github.com/strongloop/express/issues/936) +// If error handling is needed, now it's like this https://github.com/expressjs/errorhandler +docsCoServer.install(server, () => { + operationContext.global.logger.info('Start callbackFunction'); + + server.listen(config.get('services.CoAuthoring.server.port'), () => { + operationContext.global.logger.warn("Express server listening on port %d in %s mode. Version: %s. Build: %s", config.get('services.CoAuthoring.server.port'), app.settings.env, commonDefines.buildVersion, commonDefines.buildNumber); + }); + + app.get('/index.html', (req, res) => { + return co(function*() { + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + let [licenseInfo] = yield tenantManager.getTenantLicense(ctx); + let buildVersion = commonDefines.buildVersion; + let buildNumber = commonDefines.buildNumber; + let buildDate, packageType, customerId = "", alias = "", multitenancy=""; + if (licenseInfo) { + buildDate = licenseInfo.buildDate.toISOString(); + packageType = licenseInfo.packageType; + customerId = licenseInfo.customerId; + multitenancy = licenseInfo.multitenancy; } - const realUrl = req.url.substring(0, index); - res.sendFile(realUrl, sendFileOptions, (err) => { - if (err) { - logger.error(err); - res.status(400).end(); - } - }); - } else { - res.sendStatus(404) + let output = `Server is functioning normally. Version: ${buildVersion}. Build: ${buildNumber}`; + output += `. Release date: ${buildDate}. Package type: ${packageType}. Customer Id: ${customerId}`; + output += `. Multitenancy: ${multitenancy}. Alias: ${alias}`; + res.send(output); + } catch (err) { + ctx.logger.error('index.html error: %s', err.stack); + res.sendStatus(400); } }); - } + }); - // ЕÑли захочетÑÑ Ð¸Ñпользовать 'development' и 'production', - // то Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ app.settings.env (https://github.com/strongloop/express/issues/936) - // ЕÑли нужна обработка ошибок, то теперь она Ñ‚Ð°ÐºÐ°Ñ https://github.com/expressjs/errorhandler - docsCoServer.install(server, () => { - server.listen(config.get('server.port'), () => { - logger.warn("Express server listening on port %d in %s mode", config.get('server.port'), app.settings.env); - }); + app.use('/', staticRouter); - app.get('/index.html', (req, res) => { - res.send('Server is functioning normally. Version: ' + commonDefines.buildVersion + '. Build: ' + - commonDefines.buildNumber); - }); - const rawFileParser = bodyParser.raw( - {inflate: true, limit: config.get('server.limits_tempfile_upload'), type: '*/*'}); - - app.get('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer); - app.post('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, - docsCoServer.commandFromServer); - - if (config.has('server.fonts_route')) { - const fontsRoute = config.get('server.fonts_route'); - app.get('/' + fontsRoute + 'native/:fontname', fontService.getFont); - app.get('/' + fontsRoute + 'js/:fontname', fontService.getFont); - app.get('/' + fontsRoute + 'odttf/:fontname', fontService.getFont); + const rawFileParser = bodyParser.raw( + {inflate: true, limit: config.get('services.CoAuthoring.server.limits_tempfile_upload'), type: function() {return true;}}); + const urleEcodedParser = bodyParser.urlencoded({ extended: false }); + let forms = multer(); + + app.get('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer); + app.post('/coauthoring/CommandService.ashx', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer); + app.post('/command', utils.checkClientIp, rawFileParser, docsCoServer.commandFromServer); + + app.get('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convertXml); + app.post('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convertXml); + app.post('/converter', utils.checkClientIp, rawFileParser, converterService.convertJson); + + app.param('docid', (req, res, next, val) => { + if (constants.DOC_ID_REGEX.test(val)) { + next(); + } else { + res.sendStatus(403); + } + }); + app.param('index', (req, res, next, val) => { + if (!isNaN(parseInt(val))) { + next(); + } else { + res.sendStatus(403); } + }); + app.post('/upload/:docid*', rawFileParser, fileUploaderService.uploadImageFile); - app.get('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convert); - app.post('/ConvertService.ashx', utils.checkClientIp, rawFileParser, converterService.convert); + app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs); + app.post('/savefile/:docid', rawFileParser, canvasService.saveFile); + app.get('/printfile/:docid/:filename', canvasService.printFile); + app.get('/downloadfile/:docid', canvasService.downloadFile); + app.post('/downloadfile/:docid', rawFileParser, canvasService.downloadFile); + app.get('/healthcheck', utils.checkClientIp, docsCoServer.healthCheck); + app.get('/baseurl', (req, res) => { + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + //todo + // yield ctx.initTenantCache(); + res.send(utils.getBaseUrlByRequest(ctx, req)); + } catch (err) { + ctx.logger.error('baseurl error: %s', err.stack); + } + }); - app.get('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile); - app.post('/FileUploader.ashx', utils.checkClientIp, rawFileParser, fileUploaderService.uploadTempFile); + app.get('/robots.txt', (req, res) => { + res.setHeader('Content-Type', 'plain/text'); + res.send("User-agent: *\nDisallow: /"); + }); - const docIdRegExp = new RegExp("^[" + constants.DOC_ID_PATTERN + "]*$", 'i'); - app.param('docid', (req, res, next, val) => { - if (docIdRegExp.test(val)) { - next(); - } else { - res.sendStatus(403); - } - }); - app.param('index', (req, res, next, val) => { - if (!isNaN(parseInt(val))) { - next(); - } else { - res.sendStatus(403); - } - }); - app.post('/uploadold/:docid/:userid/:index/:jwt?', fileUploaderService.uploadImageFileOld); - app.post('/upload/:docid/:userid/:index/:jwt?', rawFileParser, fileUploaderService.uploadImageFile); - - app.post('/downloadas/:docid', rawFileParser, canvasService.downloadAs); - app.get('/healthcheck', utils.checkClientIp, docsCoServer.healthCheck); - - const sendUserPlugins = (res, data) => { - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(data)); - }; - app.get('/plugins.json', (req, res) => { - if (userPlugins && !updatePlugins) { - sendUserPlugins(res, userPlugins); - return; - } + app.post('/docbuilder', utils.checkClientIp, rawFileParser, (req, res) => { + converterService.builder(req, res); + }); + app.get('/info/info.json', utils.checkClientIp, docsCoServer.licenseInfo); + app.put('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown); + app.delete('/internal/cluster/inactive', utils.checkClientIp, docsCoServer.shutdown); + app.get('/internal/connections/edit', docsCoServer.getEditorConnectionsCount); - if (!config.has('server.static_content') || - !config.has('plugins.uri')) { + function checkWopiEnable(req, res, next) { + //todo may be move code into wopiClient or wopiClient.discovery... + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + ctx.initTenantCache() + .then(() => { + const tenWopiEnable = ctx.getCfg('wopi.enable', cfgWopiEnable); + if (tenWopiEnable) { + next(); + } else { + res.sendStatus(404); + } + }).catch((err) => { + ctx.logger.error('checkWopiEnable error: %s', err.stack); res.sendStatus(404); - return; - } + }); + } + function checkWopiDummyEnable(req, res, next) { + //todo may be move code into wopiClient or wopiClient.discovery... + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + ctx.initTenantCache() + .then(() => { + const tenWopiEnable = ctx.getCfg('wopi.enable', cfgWopiEnable); + const tenWopiDummyEnable = ctx.getCfg('wopi.dummy.enable', cfgWopiDummyEnable); + if (tenWopiEnable && tenWopiDummyEnable) { + next(); + } else { + res.sendStatus(404); + } + }).catch((err) => { + ctx.logger.error('checkWopiDummyEnable error: %s', err.stack); + res.sendStatus(404); + }); + } + //todo dest + let fileForms = multer({limits: {fieldSize: cfgDownloadMaxBytes}}); + app.get('/hosting/discovery', checkWopiEnable, utils.checkClientIp, wopiClient.discovery); + app.get('/hosting/capabilities', checkWopiEnable, utils.checkClientIp, wopiClient.collaboraCapabilities); + app.post('/lool/convert-to/:format?', checkWopiEnable, utils.checkClientIp, urleEcodedParser, fileForms.any(), converterService.convertTo); + app.post('/cool/convert-to/:format?', checkWopiEnable, utils.checkClientIp, urleEcodedParser, fileForms.any(), converterService.convertTo); + app.post('/hosting/wopi/:documentType/:mode', checkWopiEnable, urleEcodedParser, forms.none(), utils.lowercaseQueryString, wopiClient.getEditorHtml); + app.post('/hosting/wopi/convert-and-edit/:ext/:targetext', checkWopiEnable, urleEcodedParser, forms.none(), utils.lowercaseQueryString, wopiClient.getConverterHtml); + app.get('/hosting/wopi/convert-and-edit-handler', checkWopiEnable, utils.lowercaseQueryString, converterService.getConverterHtmlHandler); + app.get('/wopi/files/:docid', apicache.middleware("5 minutes"), checkWopiDummyEnable, utils.lowercaseQueryString, wopiClient.dummyCheckFileInfo); + app.post('/wopi/files/:docid', checkWopiDummyEnable, wopiClient.dummyOk); + app.get('/wopi/files/:docid/contents', apicache.middleware("5 minutes"), checkWopiDummyEnable, wopiClient.dummyGetFile); + app.post('/wopi/files/:docid/contents', checkWopiDummyEnable, wopiClient.dummyOk); - let staticContent = config.get('server.static_content'); - let pluginsUri = config.get('plugins.uri'); - let pluginsPath = undefined; + app.post('/dummyCallback', utils.checkClientIp, apicache.middleware("5 minutes"), rawFileParser, function(req, res){ + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + //yield ctx.initTenantCache();//no need + ctx.logger.debug(`dummyCallback req.body:%s`, req.body); + utils.fillResponseSimple(res, JSON.stringify({error: 0}, "application/json")); + }); - if (staticContent[pluginsUri]) { - pluginsPath = staticContent[pluginsUri].path; - } + const sendUserPlugins = (res, data) => { + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(data)); + }; + app.get('/plugins.json', (req, res) => { + //fs.watch is not reliable. Set cache expiry time + if (userPlugins && (new Date() - updatePluginsTime) < updatePluginsCacheExpire) { + sendUserPlugins(res, userPlugins); + return; + } - let baseUrl = utils.getBaseUrlByRequest(req); - utils.listFolders(pluginsPath, true).then((values) => { - return co(function*() { - const configFile = 'config.json'; - let stats = null; - let result = []; - for (let i = 0; i < values.length; ++i) { - try { - stats = yield utils.fsStat(path.join(values[i], configFile)); - } catch (err) { - stats = null; - } + if (!config.has('services.CoAuthoring.server.static_content') || !config.has('services.CoAuthoring.plugins.uri')) { + res.sendStatus(404); + return; + } - if (stats && stats.isFile) { - result.push( baseUrl + pluginsUri + '/' + path.basename(values[i]) + '/' + configFile); - } + let staticContent = config.get('services.CoAuthoring.server.static_content'); + let pluginsUri = config.get('services.CoAuthoring.plugins.uri'); + let pluginsPath = undefined; + let pluginsAutostart = config.get('services.CoAuthoring.plugins.autostart'); + + if (staticContent[pluginsUri]) { + pluginsPath = staticContent[pluginsUri].path; + } + + let baseUrl = '../../../..'; + utils.listFolders(pluginsPath, true).then((values) => { + return co(function*() { + const configFile = 'config.json'; + let stats = null; + let result = []; + for (let i = 0; i < values.length; ++i) { + try { + stats = yield utils.fsStat(path.join(values[i], configFile)); + } catch (err) { + stats = null; } - userPlugins = {'url': '', 'pluginsData': result}; - sendUserPlugins(res, userPlugins); - }); + if (stats && stats.isFile) { + result.push( baseUrl + pluginsUri + '/' + path.basename(values[i]) + '/' + configFile); + } + } + + updatePluginsTime = new Date(); + userPlugins = {'url': '', 'pluginsData': result, 'autostart': pluginsAutostart}; + sendUserPlugins(res, userPlugins); }); }); }); + app.get('/themes.json', apicache.middleware("5 minutes"), (req, res) => { + return co(function*() { + let themes = []; + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('themes.json start'); + if (!config.has('services.CoAuthoring.server.static_content') || !config.has('services.CoAuthoring.themes.uri')) { + return; + } + let staticContent = config.get('services.CoAuthoring.server.static_content'); + let themesUri = config.get('services.CoAuthoring.themes.uri'); + let themesList = []; - process.on('message', (msg) => { - if (!docsCoServer) { - return; - } - switch (msg.type) { - case 1: - docsCoServer.setLicenseInfo(msg.data); - break; - case 2: - updatePlugins = true; - break; - } + for (let i in staticContent) { + if (staticContent.hasOwnProperty(i) && themesUri.startsWith(i)) { + let dir = staticContent[i].path + themesUri.substring(i.length); + themesList = yield utils.listObjects(dir, true); + ctx.logger.debug('themes.json dir:%s', dir); + ctx.logger.debug('themes.json themesList:%j', themesList); + for (let j = 0; j < themesList.length; ++j) { + if (themesList[j].endsWith('.json')) { + try { + let data = yield utils.readFile(themesList[j], true); + let text = new TextDecoder('utf-8', {ignoreBOM: false}).decode(data); + themes.push(JSON.parse(text)); + } catch (err) { + ctx.logger.error('themes.json file:%s error:%s', themesList[j], err.stack); + } + } + } + break; + } + } + } catch (err) { + ctx.logger.error('themes.json error:%s', err.stack); + } finally { + if (themes.length > 0) { + res.setHeader('Content-Type', 'application/json'); + res.send({"themes": themes}); + } else { + res.sendStatus(404); + } + ctx.logger.info('themes.json end'); + } + }); }); -} + app.get('/document_editor_service_worker.js', apicache.middleware("5 min"), async (req, res) => { + //make handler only for development version + res.sendFile(path.resolve("../../sdkjs/common/serviceworker/document_editor_service_worker.js")); + }); + app.use((err, req, res, next) => { + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + ctx.logger.error('default error handler:%s', err.stack); + res.sendStatus(500); + }); +}); process.on('uncaughtException', (err) => { - logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); - logger.error(err.stack); + operationContext.global.logger.error('uncaughtException:%s', err.stack); logger.shutdown(() => { process.exit(1); }); diff --git a/DocService/sources/shutdown.js b/DocService/sources/shutdown.js index 0b2fa68aa9..273889f606 100644 --- a/DocService/sources/shutdown.js +++ b/DocService/sources/shutdown.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -35,72 +35,66 @@ var config = require('config'); var configCoAuthoring = config.get('services.CoAuthoring'); var co = require('co'); var logger = require('./../../Common/sources/logger'); -var pubsubService = require('./' + configCoAuthoring.get('pubsub.name')); -var pubsubRedis = require('./pubsubRedis.js'); +var pubsubService = require('./pubsubRabbitMQ'); +const sqlBase = require('./databaseConnectors/baseConnector'); var commonDefines = require('./../../Common/sources/commondefines'); var constants = require('./../../Common/sources/constants'); var utils = require('./../../Common/sources/utils'); var cfgRedisPrefix = configCoAuthoring.get('redis.prefix'); var redisKeyShutdown = cfgRedisPrefix + constants.REDIS_KEY_SHUTDOWN; -var redisKeyDocuments = cfgRedisPrefix + constants.REDIS_KEY_DOCUMENTS; var WAIT_TIMEOUT = 30000; var LOOP_TIMEOUT = 1000; -var EXEC_TIMEOUT = WAIT_TIMEOUT + utils.CONVERTION_TIMEOUT; +var EXEC_TIMEOUT = WAIT_TIMEOUT + utils.getConvertionTimeout(undefined); -(function shutdown() { - return co(function* () { - var exitCode = 0; +exports.shutdown = function(ctx, editorStat, status) { + return co(function*() { + var res = true; try { - logger.debug('shutdown start' + EXEC_TIMEOUT); + ctx.logger.debug('shutdown start:' + EXEC_TIMEOUT); - var redisClient = pubsubRedis.getClientRedis(); - //redisKeyShutdown не проÑтой Ñчетчик, чтобы его не уменьшала Ñборка, ÐºÐ¾Ñ‚Ð¾Ñ€Ð°Ñ Ð½Ð°Ñ‡Ð°Ð»Ð°ÑÑŒ перед запуÑком Shutdown - //ÑбраÑываем redisKeyShutdown на вÑÑкий Ñлучай, еÑли предыдущий запуÑк не дошел до конца - var multi = redisClient.multi([ - ['del', redisKeyShutdown], - ['zcard', redisKeyDocuments] - ]); - var multiRes = yield utils.promiseRedis(multi, multi.exec); - logger.debug('number of open documents %d', multiRes[1]); + //redisKeyShutdown is not a simple counter, so it doesn't get decremented by a build that started before Shutdown started + //reset redisKeyShutdown just in case the previous run didn't finish + yield editorStat.cleanupShutdown(redisKeyShutdown); var pubsub = new pubsubService(); yield pubsub.initPromise(); //inner ping to update presence - logger.debug('shutdown pubsub shutdown message'); - pubsub.publish(JSON.stringify({type: commonDefines.c_oPublishType.shutdown})); + ctx.logger.debug('shutdown pubsub shutdown message'); + yield pubsub.publish(JSON.stringify({type: commonDefines.c_oPublishType.shutdown, ctx: ctx, status: status})); //wait while pubsub deliver and start conversion - logger.debug('shutdown start wait pubsub deliver'); + ctx.logger.debug('shutdown start wait pubsub deliver'); var startTime = new Date().getTime(); var isStartWait = true; while (true) { var curTime = new Date().getTime() - startTime; if (isStartWait && curTime >= WAIT_TIMEOUT) { isStartWait = false; - logger.debug('shutdown stop wait pubsub deliver'); - } else if(curTime >= EXEC_TIMEOUT) { - exitCode = 1; - logger.debug('shutdown timeout'); + ctx.logger.debug('shutdown stop wait pubsub deliver'); + } else if (curTime >= EXEC_TIMEOUT) { + res = false; + ctx.logger.debug('shutdown timeout'); break; } - var remainingFiles = yield utils.promiseRedis(redisClient, redisClient.scard, redisKeyShutdown); - logger.debug('shutdown remaining files:%d', remainingFiles); - if (!isStartWait && remainingFiles <= 0) { + var remainingFiles = yield editorStat.getShutdownCount(redisKeyShutdown); + let inSavingStatus = yield sqlBase.getCountWithStatus(ctx, commonDefines.FileStatus.SaveVersion, EXEC_TIMEOUT); + ctx.logger.debug('shutdown remaining files editorStat:%d, db:%d', remainingFiles, inSavingStatus); + if (!isStartWait && (remainingFiles + inSavingStatus) <= 0) { break; } yield utils.sleep(LOOP_TIMEOUT); } - //todo надо проверÑть очереди, потому что могут быть долгие конвертации запущенные до Shutdown + //todo need to check the queues, because there may be long conversions running before Shutdown //clean up - yield utils.promiseRedis(redisClient, redisClient.del, redisKeyShutdown); + yield editorStat.cleanupShutdown(redisKeyShutdown); yield pubsub.close(); - logger.debug('shutdown end'); + ctx.logger.debug('shutdown end'); } catch (e) { - logger.error('shutdown error:\r\n%s', e.stack); - } finally { - process.exit(exitCode); + res = false; + ctx.logger.error('shutdown error: %s', e.stack); } + return res; }); -})(); +}; diff --git a/DocService/sources/taskresult.js b/DocService/sources/taskresult.js index 21c16f7d6c..4a5fd6be0d 100644 --- a/DocService/sources/taskresult.js +++ b/DocService/sources/taskresult.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,42 +32,45 @@ 'use strict'; -var sqlBase = require('./baseConnector'); -var logger = require('./../../Common/sources/logger'); -var utils = require('./../../Common/sources/utils'); +const crypto = require('crypto'); +var sqlBase = require('./databaseConnectors/baseConnector'); var constants = require('./../../Common/sources/constants'); +var commonDefines = require('./../../Common/sources/commondefines'); +var tenantManager = require('./../../Common/sources/tenantManager'); +var config = require('config'); -var RANDOM_KEY_MAX = 10000; +const cfgTableResult = config.get('services.CoAuthoring.sql.tableResult'); -var FileStatus = { - None: 0, - Ok: 1, - WaitQueue: 2, - NeedParams: 3, - Convert: 4, - Err: 5, - ErrToReload: 6, - SaveVersion: 7, - UpdateVersion: 8, - NeedPassword: 9 -}; +let addSqlParam = sqlBase.addSqlParameter; +let concatParams = sqlBase.concatParams; + +var RANDOM_KEY_MAX = 10000; function TaskResultData() { + this.tenant = null; this.key = null; this.status = null; this.statusInfo = null; this.lastOpenDate = null; + this.creationDate = null; this.userIndex = null; this.changeId = null; this.callback = null; this.baseurl = null; + this.password = null; + this.additional = null; + + this.innerPasswordChange = null;//not a DB field } TaskResultData.prototype.completeDefaults = function() { + if (!this.tenant) { + this.tenant = tenantManager.getDefautTenant(); + } if (!this.key) { this.key = ''; } if (!this.status) { - this.status = FileStatus.None; + this.status = commonDefines.FileStatus.None; } if (!this.statusInfo) { this.statusInfo = constants.NO_ERROR; @@ -75,6 +78,9 @@ TaskResultData.prototype.completeDefaults = function() { if (!this.lastOpenDate) { this.lastOpenDate = new Date(); } + if (!this.creationDate) { + this.creationDate = new Date(); + } if (!this.userIndex) { this.userIndex = 1; } @@ -89,126 +95,179 @@ TaskResultData.prototype.completeDefaults = function() { } }; -function upsert(task, opt_updateUserIndex) { - return sqlBase.baseConnector.upsert(task, opt_updateUserIndex); -} - -function getSelectString(docId) { - return 'SELECT * FROM task_result WHERE id=' + sqlBase.baseConnector.sqlEscape(docId) + ';'; +function upsert(ctx, task) { + return sqlBase.upsert(ctx, task); } -function select(docId) { +function select(ctx, docId) { return new Promise(function(resolve, reject) { - var sqlCommand = getSelectString(docId); - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let values = []; + let p1 = addSqlParam(ctx.tenant, values); + let p2 = addSqlParam(docId, values); + let sqlCommand = `SELECT * FROM ${cfgTableResult} WHERE tenant=${p1} AND id=${p2};`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { resolve(result); } - }); + }, undefined, undefined, values); }); } -function toUpdateArray(task, updateTime) { +function toUpdateArray(task, updateTime, isMask, values, setPassword) { var res = []; if (null != task.status) { - res.push('status=' + sqlBase.baseConnector.sqlEscape(task.status)); + let sqlParam = addSqlParam(task.status, values); + res.push(`status=${sqlParam}`); } if (null != task.statusInfo) { - res.push('status_info=' + sqlBase.baseConnector.sqlEscape(task.statusInfo)); + let sqlParam = addSqlParam(task.statusInfo, values); + res.push(`status_info=${sqlParam}` ); + } if (updateTime) { - res.push('last_open_date=' + sqlBase.baseConnector.sqlEscape(sqlBase.getDateTime(new Date()))); + let sqlParam = addSqlParam(new Date(), values); + res.push(`last_open_date=${sqlParam}`); + } if (null != task.indexUser) { - res.push('user_index=' + sqlBase.baseConnector.sqlEscape(task.indexUser)); + let sqlParam = addSqlParam(task.indexUser, values); + res.push(`user_index=${sqlParam}`); + } if (null != task.changeId) { - res.push('change_id=' + sqlBase.baseConnector.sqlEscape(task.changeId)); + let sqlParam = addSqlParam(task.changeId, values); + res.push(`change_id=${sqlParam}`); + } - if (null != task.callback) { - res.push('callback=' + sqlBase.baseConnector.sqlEscape(task.callback)); + if (null != task.callback && !isMask) { + var userCallback = new sqlBase.UserCallback(); + userCallback.fromValues(task.indexUser, task.callback); + let sqlParam = addSqlParam(userCallback.toSQLInsert(), values); + res.push(`callback=${concatParams('callback', sqlParam)}`); } if (null != task.baseurl) { - res.push('baseurl=' + sqlBase.baseConnector.sqlEscape(task.baseurl)); + let sqlParam = addSqlParam(task.baseurl, values); + res.push(`baseurl=${sqlParam}`); + } + if (setPassword) { + let sqlParam = addSqlParam(task.password, values); + res.push(`password=${sqlParam}`); + } else if (null != task.password || setPassword) { + var documentPassword = new sqlBase.DocumentPassword(); + documentPassword.fromValues(task.password, task.innerPasswordChange); + let sqlParam = addSqlParam(documentPassword.toSQLInsert(), values); + res.push(`password=${concatParams('password', sqlParam)}`); + } + if (null != task.additional) { + let sqlParam = addSqlParam(task.additional, values); + res.push(`additional=${concatParams('additional', sqlParam)}`); } return res; } -function getUpdateString(task) { - var commandArgEsc = toUpdateArray(task, true); - return 'UPDATE task_result SET ' + commandArgEsc.join(', ') + - ' WHERE id=' + sqlBase.baseConnector.sqlEscape(task.key) + ';'; -} -function update(task) { +function update(ctx, task, setPassword) { return new Promise(function(resolve, reject) { - var sqlCommand = getUpdateString(task); - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let values = []; + let updateElems = toUpdateArray(task, true, false, values, setPassword); + let sqlSet = updateElems.join(', '); + let p1 = addSqlParam(task.tenant, values); + let p2 = addSqlParam(task.key, values); + let sqlCommand = `UPDATE ${cfgTableResult} SET ${sqlSet} WHERE tenant=${p1} AND id=${p2};`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { resolve(result); } - }); + }, undefined, undefined, values); }); } -function getUpdateIfString(task, mask) { - var commandArgEsc = toUpdateArray(task, true); - var commandArgEscMask = toUpdateArray(mask); - commandArgEscMask.push('id=' + sqlBase.baseConnector.sqlEscape(mask.key)); - return 'UPDATE task_result SET ' + commandArgEsc.join(', ') + - ' WHERE ' + commandArgEscMask.join(' AND ') + ';'; -} -function updateIf(task, mask) { +function updateIf(ctx, task, mask) { return new Promise(function(resolve, reject) { - var sqlCommand = getUpdateIfString(task, mask); - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let values = []; + let commandArg = toUpdateArray(task, true, false, values, false); + let commandArgMask = toUpdateArray(mask, false, true, values, false); + commandArgMask.push('tenant=' + addSqlParam(mask.tenant, values)); + commandArgMask.push('id=' + addSqlParam(mask.key, values)); + let sqlSet = commandArg.join(', '); + let sqlWhere = commandArgMask.join(' AND '); + let sqlCommand = `UPDATE ${cfgTableResult} SET ${sqlSet} WHERE ${sqlWhere};`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { resolve(result); } - }); + }, undefined, undefined, values); }); } - -function getInsertString(task) { - var dateNow = sqlBase.getDateTime(new Date()); - task.completeDefaults(); - var commandArg = [task.key, task.status, task.statusInfo, dateNow, task.userIndex, task.changeId, task.callback, task.baseurl]; - var commandArgEsc = commandArg.map(function(curVal) { - return sqlBase.baseConnector.sqlEscape(curVal) +function restoreInitialPassword(ctx, docId) { + return select(ctx, docId).then(function(selectRes) { + if (selectRes.length > 0) { + var row = selectRes[0]; + let docPassword = sqlBase.DocumentPassword.prototype.getDocPassword(ctx, row.password); + var updateTask = new TaskResultData(); + updateTask.tenant = ctx.tenant; + updateTask.key = docId; + if (docPassword.initial) { + var documentPassword = new sqlBase.DocumentPassword(); + documentPassword.fromValues(docPassword.initial); + updateTask.password = documentPassword.toSQLInsert(); + return update(ctx, updateTask, true); + } else if (docPassword.current) { + updateTask.password = null; + return update(ctx, updateTask, true); + } + } }); - return 'INSERT INTO task_result ( id, status, status_info, last_open_date, user_index, change_id, callback,' + - ' baseurl) VALUES (' + commandArgEsc.join(', ') + ');'; } -function addRandomKey(task) { + +function addRandomKey(ctx, task, key, opt_prefix, opt_size) { return new Promise(function(resolve, reject) { - task.key = task.key + '_' + Math.round(Math.random() * RANDOM_KEY_MAX); - var sqlCommand = getInsertString(task); - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + task.tenant = ctx.tenant; + if (undefined !== opt_prefix && undefined !== opt_size) { + task.key = opt_prefix + crypto.randomBytes(opt_size).toString("hex"); + } else { + task.key = key + '_' + Math.round(Math.random() * RANDOM_KEY_MAX); + } + task.completeDefaults(); + let values = []; + let p0 = addSqlParam(task.tenant, values); + let p1 = addSqlParam(task.key, values); + let p2 = addSqlParam(task.status, values); + let p3 = addSqlParam(task.statusInfo, values); + let p4 = addSqlParam(new Date(), values); + let p5 = addSqlParam(task.userIndex, values); + let p6 = addSqlParam(task.changeId, values); + let p7 = addSqlParam(task.callback, values); + let p8 = addSqlParam(task.baseurl, values); + let sqlCommand = `INSERT INTO ${cfgTableResult} (tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl)` + + ` VALUES (${p0}, ${p1}, ${p2}, ${p3}, ${p4}, ${p5}, ${p6}, ${p7}, ${p8});`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { resolve(result); } - }); + }, undefined, true, values); }); } -function* addRandomKeyTask(key) { +function* addRandomKeyTask(ctx, key, opt_prefix, opt_size) { var task = new TaskResultData(); + task.tenant = ctx.tenant; task.key = key; - task.status = FileStatus.WaitQueue; - //nTryCount чтобы не завиÑнуть еÑли реально будут проблемы Ñ DB + task.status = commonDefines.FileStatus.WaitQueue; + //nTryCount so as not to freeze if there are really problems with the DB var nTryCount = RANDOM_KEY_MAX; var addRes = null; while (nTryCount-- > 0) { try { - addRes = yield addRandomKey(task); + addRes = yield addRandomKey(ctx, task, key, opt_prefix, opt_size); } catch (e) { addRes = null; - //key exist, try again + ctx.logger.debug("addRandomKeyTask %s exists, try again", task.key); } if (addRes && addRes.affectedRows > 0) { break; @@ -221,47 +280,46 @@ function* addRandomKeyTask(key) { } } -function getRemoveString(docId) { - return 'DELETE FROM task_result WHERE id=' + sqlBase.baseConnector.sqlEscape(docId) + ';'; -} -function remove(docId) { +function remove(ctx, docId) { return new Promise(function(resolve, reject) { - var sqlCommand = getRemoveString(docId); - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let values = []; + let p1 = addSqlParam(ctx.tenant, values); + let p2 = addSqlParam(docId, values); + const sqlCommand = `DELETE FROM ${cfgTableResult} WHERE tenant=${p1} AND id=${p2};`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { resolve(result); } - }); + }, undefined, undefined, values); }); } -function getExpiredString(maxCount, expireSeconds) { - var expireDate = new Date(); - utils.addSeconds(expireDate, -expireSeconds); - var expireDateStr = sqlBase.baseConnector.sqlEscape(sqlBase.getDateTime(expireDate)); - return 'SELECT * FROM task_result WHERE last_open_date <= ' + expireDateStr + - ' AND NOT EXISTS(SELECT id FROM doc_changes WHERE doc_changes.id = task_result.id LIMIT 1) LIMIT ' + maxCount + ';'; -} -function getExpired(maxCount, expireSeconds) { +function removeIf(ctx, mask) { return new Promise(function(resolve, reject) { - var sqlCommand = getExpiredString(maxCount, expireSeconds); - sqlBase.baseConnector.sqlQuery(sqlCommand, function(error, result) { + let values = []; + let commandArgMask = toUpdateArray(mask, false, true, values, false); + commandArgMask.push('tenant=' + addSqlParam(mask.tenant, values)); + commandArgMask.push('id=' + addSqlParam(mask.key, values)); + let sqlWhere = commandArgMask.join(' AND '); + const sqlCommand = `DELETE FROM ${cfgTableResult} WHERE ${sqlWhere};`; + sqlBase.sqlQuery(ctx, sqlCommand, function(error, result) { if (error) { reject(error); } else { resolve(result); } - }); + }, undefined, undefined, values); }); } -exports.FileStatus = FileStatus; exports.TaskResultData = TaskResultData; exports.upsert = upsert; exports.select = select; exports.update = update; exports.updateIf = updateIf; +exports.restoreInitialPassword = restoreInitialPassword; exports.addRandomKeyTask = addRandomKeyTask; exports.remove = remove; -exports.getExpired = getExpired; +exports.removeIf = removeIf; +exports.getExpired = sqlBase.getExpired; diff --git a/DocService/sources/utilsDocService.js b/DocService/sources/utilsDocService.js new file mode 100644 index 0000000000..17ccb61d0d --- /dev/null +++ b/DocService/sources/utilsDocService.js @@ -0,0 +1,157 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const util = require("util"); +const config = require('config'); +const exifParser = require('exif-parser'); +//set global window to fix issue https://github.com/photopea/UTIF.js/issues/130 +if (!global.window) { + global.window = global; +} +const Jimp = require('jimp'); +const locale = require('windows-locale'); +const ms = require('ms'); + +const tenantManager = require('../../Common/sources/tenantManager'); +const { notificationTypes, ...notificationService } = require('../../Common/sources/notificationService'); + +const cfgStartNotifyFrom = ms(config.get('license.warning_license_expiration')); +const cfgNotificationRuleLicenseExpirationWarning = config.get('notification.rules.licenseExpirationWarning.template'); +const cfgNotificationRuleLicenseExpirationError = config.get('notification.rules.licenseExpirationError.template'); + +async function fixImageExifRotation(ctx, buffer) { + if (!buffer) { + return buffer; + } + //todo move to DocService dir common + try { + let parser = exifParser.create(buffer); + let exif = parser.parse(); + if (exif.tags?.Orientation > 1) { + ctx.logger.debug('fixImageExifRotation remove exif and rotate:%j', exif); + buffer = convertImageTo(ctx, buffer, Jimp.AUTO); + } + } catch (e) { + ctx.logger.debug('fixImageExifRotation error:%s', e.stack); + } + return buffer; +} +async function convertImageToPng(ctx, buffer) { + return await convertImageTo(ctx, buffer, Jimp.MIME_PNG); +} +async function convertImageTo(ctx, buffer, mime) { + try { + ctx.logger.debug('convertImageTo %s', mime); + let image = await Jimp.read(buffer); + //remove exif + image.bitmap.exifBuffer = undefined; + //set jpeg and png quality + //https://www.imagemagick.org/script/command-line-options.php#quality + image.quality(90); + image.deflateLevel(7); + buffer = await image.getBufferAsync(mime); + } catch (e) { + ctx.logger.debug('convertImageTo error:%s', e.stack); + } + return buffer; +} +/** + * + * @param {string} lang + * @returns {number | undefined} + */ +function localeToLCID(lang) { + let elem = locale[lang && lang.toLowerCase()]; + return elem && elem.id; +} + +function humanFriendlyExpirationTime(endTime) { + const month = [ + 'January', + 'February', + 'March', + 'April', + 'May', + 'June', + 'July', + 'August', + 'September', + 'October', + 'November', + 'December' + ]; + + return `${month[endTime.getUTCMonth()]} ${endTime.getUTCDate()}, ${endTime.getUTCFullYear()}` +} + +/** + * Notify server user about license expiration via configured notification transports. + * @param {Context} ctx Context. + * @param {Date} endDate Date of expiration. + * @returns {undefined} + */ +async function notifyLicenseExpiration(ctx, endDate) { + if (!endDate) { + ctx.logger.warn('notifyLicenseExpiration(): expiration date is not defined'); + return; + } + + const currentDate = new Date(); + if (currentDate.getTime() >= endDate.getTime() - cfgStartNotifyFrom) { + //todo remove stub for "new Date(1)" and "setMonth + 1" in license.js; bug 70676 + if (endDate.getUTCFullYear() < 2000) { + endDate = currentDate; + } + const formattedExpirationTime = humanFriendlyExpirationTime(endDate); + const applicationName = (process.env.APPLICATION_NAME || "").toUpperCase(); + if (endDate <= currentDate) { + const tenNotificationRuleLicenseExpirationError = ctx.getCfg('notification.rules.licenseExpirationError.template', cfgNotificationRuleLicenseExpirationError); + const title = util.format(tenNotificationRuleLicenseExpirationError.title, applicationName); + const message = util.format(tenNotificationRuleLicenseExpirationError.body, formattedExpirationTime); + ctx.logger.error(message); + await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_ERROR, title, message); + } else { + const tenNotificationRuleLicenseExpirationWarning = ctx.getCfg('notification.rules.licenseExpirationWarning.template', cfgNotificationRuleLicenseExpirationWarning); + const title = util.format(tenNotificationRuleLicenseExpirationWarning.title, applicationName); + const message = util.format(tenNotificationRuleLicenseExpirationWarning.body, formattedExpirationTime); + ctx.logger.warn(message); + await notificationService.notify(ctx, notificationTypes.LICENSE_EXPIRATION_WARNING, title, message); + } + } +} + +module.exports.fixImageExifRotation = fixImageExifRotation; +module.exports.convertImageToPng = convertImageToPng; +module.exports.localeToLCID = localeToLCID; +module.exports.notifyLicenseExpiration = notifyLicenseExpiration; diff --git a/DocService/sources/wopiClient.js b/DocService/sources/wopiClient.js new file mode 100644 index 0000000000..ce72847fee --- /dev/null +++ b/DocService/sources/wopiClient.js @@ -0,0 +1,1181 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const path = require('path'); +const { pipeline } = require('node:stream/promises'); +const crypto = require('crypto'); +let util = require('util'); +const {URL} = require('url'); +const co = require('co'); +const jwt = require('jsonwebtoken'); +const config = require('config'); +const { createReadStream } = require('fs'); +const { stat, lstat, readdir } = require('fs/promises'); +const utf7 = require('utf7'); +const mimeDB = require('mime-db'); +const xmlbuilder2 = require('xmlbuilder2'); +const logger = require('./../../Common/sources/logger'); +const utils = require('./../../Common/sources/utils'); +const constants = require('./../../Common/sources/constants'); +const commonDefines = require('./../../Common/sources/commondefines'); +const formatChecker = require('./../../Common/sources/formatchecker'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); +const sqlBase = require('./databaseConnectors/baseConnector'); +const taskResult = require('./taskresult'); +const canvasService = require('./canvasservice'); +const converterService = require('./converterservice'); +const mime = require('mime'); +const license = require('./../../Common/sources/license'); + +const cfgTokenOutboxAlgorithm = config.get('services.CoAuthoring.token.outbox.algorithm'); +const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires'); +const cfgTokenEnableBrowser = config.get('services.CoAuthoring.token.enable.browser'); +const cfgCallbackRequestTimeout = config.get('services.CoAuthoring.server.callbackRequestTimeout'); +const cfgNewFileTemplate = config.get('services.CoAuthoring.server.newFileTemplate'); +const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout'); +const cfgMaxDownloadBytes = config.get('FileConverter.converter.maxDownloadBytes'); +const cfgWopiFileInfoBlockList = config.get('wopi.fileInfoBlockList'); +const cfgWopiWopiZone = config.get('wopi.wopiZone'); +const cfgWopiPdfView = config.get('wopi.pdfView'); +const cfgWopiPdfEdit = config.get('wopi.pdfEdit'); +const cfgWopiWordView = config.get('wopi.wordView'); +const cfgWopiWordEdit = config.get('wopi.wordEdit'); +const cfgWopiCellView = config.get('wopi.cellView'); +const cfgWopiCellEdit = config.get('wopi.cellEdit'); +const cfgWopiSlideView = config.get('wopi.slideView'); +const cfgWopiSlideEdit = config.get('wopi.slideEdit'); +const cfgWopiDiagramView = config.get('wopi.diagramView'); +const cfgWopiDiagramEdit = config.get('wopi.diagramEdit'); +const cfgWopiForms = config.get('wopi.forms'); +const cfgWopiFavIconUrlWord = config.get('wopi.favIconUrlWord'); +const cfgWopiFavIconUrlCell = config.get('wopi.favIconUrlCell'); +const cfgWopiFavIconUrlSlide = config.get('wopi.favIconUrlSlide'); +const cfgWopiFavIconUrlPdf = config.get('wopi.favIconUrlPdf'); +const cfgWopiFavIconUrlDiagram = config.get('wopi.favIconUrlDiagram'); +const cfgWopiPublicKey = config.get('wopi.publicKey'); +const cfgWopiModulus = config.get('wopi.modulus'); +const cfgWopiExponent = config.get('wopi.exponent'); +const cfgWopiPrivateKey = config.get('wopi.privateKey'); +const cfgWopiPublicKeyOld = config.get('wopi.publicKeyOld'); +const cfgWopiModulusOld = config.get('wopi.modulusOld'); +const cfgWopiExponentOld = config.get('wopi.exponentOld'); +const cfgWopiPrivateKeyOld = config.get('wopi.privateKeyOld'); +const cfgWopiHost = config.get('wopi.host'); +const cfgWopiDummySampleFilePath = config.get('wopi.dummy.sampleFilePath'); + +let cryptoSign = util.promisify(crypto.sign); + +let templatesFolderLocalesCache = null; +let templatesFolderExtsCache = null; +const templateFilesSizeCache = {}; +let shutdownFlag = false; + +//patch mimeDB +if (!mimeDB["application/vnd.visio2013"]) { + mimeDB["application/vnd.visio2013"] = {extensions: ["vsdx", "vstx", "vssx", "vsdm", "vstm", "vssm"]}; +} + +let mimeTypesByExt = (function() { + let mimeTypesByExt = {}; + for (let mimeType in mimeDB) { + if (mimeDB.hasOwnProperty(mimeType)) { + let val = mimeDB[mimeType]; + if (val.extensions) { + val.extensions.forEach((value) => { + if (!mimeTypesByExt[value]) { + mimeTypesByExt[value] = []; + } + mimeTypesByExt[value].push(mimeType); + }) + } + } + } + return mimeTypesByExt; +})(); + +async function getTemplatesFolderExts(ctx){ + //find available template files + if (templatesFolderExtsCache === null) { + const tenNewFileTemplate = ctx.getCfg('services.CoAuthoring.server.newFileTemplate', cfgNewFileTemplate); + const dirContent = await readdir(`${tenNewFileTemplate}/${constants.TEMPLATES_DEFAULT_LOCALE}/`, { withFileTypes: true }); + templatesFolderExtsCache = dirContent + .filter(dirObject => dirObject.isFile()) + .reduce((result, item, index, array) => { + let ext = path.extname(item.name).substring(1); + result[ext] = ext; + return result; + }, {}); + } + return templatesFolderExtsCache; +} + +function discovery(req, res) { + return co(function*() { + const xml = xmlbuilder2.create({version: '1.0', encoding: 'utf-8'}); + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('wopiDiscovery start'); + const tenWopiWopiZone = ctx.getCfg('wopi.wopiZone', cfgWopiWopiZone); + const tenWopiPdfView = ctx.getCfg('wopi.pdfView', cfgWopiPdfView); + const tenWopiPdfEdit = ctx.getCfg('wopi.pdfEdit', cfgWopiPdfEdit); + const tenWopiWordView = ctx.getCfg('wopi.wordView', cfgWopiWordView); + const tenWopiWordEdit = ctx.getCfg('wopi.wordEdit', cfgWopiWordEdit); + const tenWopiCellView = ctx.getCfg('wopi.cellView', cfgWopiCellView); + const tenWopiCellEdit = ctx.getCfg('wopi.cellEdit', cfgWopiCellEdit); + const tenWopiSlideView = ctx.getCfg('wopi.slideView', cfgWopiSlideView); + const tenWopiSlideEdit = ctx.getCfg('wopi.slideEdit', cfgWopiSlideEdit); + const tenWopiDiagramView = ctx.getCfg('wopi.diagramView', cfgWopiDiagramView); + const tenWopiDiagramEdit = ctx.getCfg('wopi.diagramEdit', cfgWopiDiagramEdit); + const tenWopiForms = ctx.getCfg('wopi.forms', cfgWopiForms); + const tenWopiFavIconUrlWord = ctx.getCfg('wopi.favIconUrlWord', cfgWopiFavIconUrlWord); + const tenWopiFavIconUrlCell = ctx.getCfg('wopi.favIconUrlCell', cfgWopiFavIconUrlCell); + const tenWopiFavIconUrlSlide = ctx.getCfg('wopi.favIconUrlSlide', cfgWopiFavIconUrlSlide); + const tenWopiFavIconUrlPdf = ctx.getCfg('wopi.favIconUrlPdf', cfgWopiFavIconUrlPdf); + const tenWopiFavIconUrlDiagram = ctx.getCfg('wopi.favIconUrlDiagram', cfgWopiFavIconUrlDiagram); + const tenWopiPublicKey = ctx.getCfg('wopi.publicKey', cfgWopiPublicKey); + const tenWopiModulus = ctx.getCfg('wopi.modulus', cfgWopiModulus); + const tenWopiExponent = ctx.getCfg('wopi.exponent', cfgWopiExponent); + const tenWopiPublicKeyOld = ctx.getCfg('wopi.publicKeyOld', cfgWopiPublicKeyOld); + const tenWopiModulusOld = ctx.getCfg('wopi.modulusOld', cfgWopiModulusOld); + const tenWopiExponentOld = ctx.getCfg('wopi.exponentOld', cfgWopiExponentOld); + const tenWopiHost = ctx.getCfg('wopi.host', cfgWopiHost); + + let baseUrl = tenWopiHost || utils.getBaseUrlByRequest(ctx, req); + let names = ['Word','Excel','PowerPoint','Pdf']; + let favIconUrls = [tenWopiFavIconUrlWord, tenWopiFavIconUrlCell, tenWopiFavIconUrlSlide, tenWopiFavIconUrlPdf]; + let exts = [ + {targetext: 'docx', view: tenWopiWordView, edit: tenWopiWordEdit}, + {targetext: 'xlsx', view: tenWopiCellView, edit: tenWopiCellEdit}, + {targetext: 'pptx', view: tenWopiSlideView, edit: tenWopiSlideEdit}, + {targetext: null, view: tenWopiPdfView, edit: tenWopiPdfEdit} + ]; + let documentTypes = [`word`, `cell`, `slide`, `pdf`]; + //todo check sdkjs-ooxml addon + let addVisio = (tenWopiDiagramView.length > 0 || tenWopiDiagramEdit.length > 0) + && (constants.PACKAGE_TYPE_OS !== license.packageType || process.env?.NODE_ENV?.startsWith("development-")); + if (addVisio) { + names.push('Visio'); + favIconUrls.push(tenWopiFavIconUrlDiagram); + exts.push({targetext: null, view: tenWopiDiagramView, edit: tenWopiDiagramEdit}); + documentTypes.push(`diagram`); + } + + let templatesFolderExtsCache = yield getTemplatesFolderExts(ctx); + let formsExts = tenWopiForms.reduce((result, item, index, array) => { + result[item] = item; + return result; + }, {}); + + let templateStart = `${baseUrl}/hosting/wopi`; + let templateEnd = `<rs=DC_LLCC&><dchat=DISABLE_CHAT&><embed=EMBEDDED&>`; + templateEnd += `<fs=FULLSCREEN&><hid=HOST_SESSION_ID&><rec=RECORDING&>`; + templateEnd += `<sc=SESSION_CONTEXT&><thm=THEME_ID&><ui=UI_LLCC&>`; + templateEnd += `<wopisrc=WOPI_SOURCE&>&`; + let xmlZone = xml.ele('wopi-discovery').ele('net-zone', { name: tenWopiWopiZone }); + //start section for MS WOPI connectors + for(let i = 0; i < names.length; ++i) { + let name = names[i]; + let favIconUrl = favIconUrls[i]; + if (!(favIconUrl.startsWith('http://') || favIconUrl.startsWith('https://'))) { + favIconUrl = baseUrl + favIconUrl; + } + let ext = exts[i]; + let urlTemplateView = `${templateStart}/${documentTypes[i]}/view?${templateEnd}`; + let urlTemplateEmbedView = `${templateStart}/${documentTypes[i]}/view?embed=1&${templateEnd}`; + let urlTemplateMobileView = `${templateStart}/${documentTypes[i]}/view?mobile=1&${templateEnd}`; + let urlTemplateEdit = `${templateStart}/${documentTypes[i]}/edit?${templateEnd}`; + let urlTemplateMobileEdit = `${templateStart}/${documentTypes[i]}/edit?mobile=1&${templateEnd}`; + let urlTemplateFormSubmit = `${templateStart}/${documentTypes[i]}/edit?formsubmit=1&${templateEnd}`; + let xmlApp = xmlZone.ele('app', {name: name, favIconUrl: favIconUrl}); + for (let j = 0; j < ext.view.length; ++j) { + xmlApp.ele('action', {name: 'view', ext: ext.view[j], default: 'true', urlsrc: urlTemplateView}).up(); + xmlApp.ele('action', {name: 'embedview', ext: ext.view[j], urlsrc: urlTemplateEmbedView}).up(); + xmlApp.ele('action', {name: 'mobileView', ext: ext.view[j], urlsrc: urlTemplateMobileView}).up(); + if (ext.targetext) { + let urlConvert = `${templateStart}/convert-and-edit/${ext.view[j]}/${ext.targetext}?${templateEnd}`; + xmlApp.ele('action', {name: 'convert', ext: ext.view[j], targetext: ext.targetext, requires: 'update', urlsrc: urlConvert}).up(); + } + } + for (let j = 0; j < ext.edit.length; ++j) { + xmlApp.ele('action', {name: 'view', ext: ext.edit[j], urlsrc: urlTemplateView}).up(); + xmlApp.ele('action', {name: 'embedview', ext: ext.edit[j], urlsrc: urlTemplateEmbedView}).up(); + xmlApp.ele('action', {name: 'mobileView', ext: ext.edit[j], urlsrc: urlTemplateMobileView}).up(); + if (formsExts[ext.edit[j]]) { + xmlApp.ele('action', {name: 'edit', ext: ext.edit[j], default: 'true', requires: 'locks,update', urlsrc: urlTemplateEdit}).up(); + xmlApp.ele('action', {name: 'formsubmit', ext: ext.edit[j], requires: 'locks,update', urlsrc: urlTemplateFormSubmit}).up(); + } else { + xmlApp.ele('action', {name: 'edit', ext: ext.edit[j], default: 'true', requires: 'locks,update', urlsrc: urlTemplateEdit}).up(); + } + xmlApp.ele('action', {name: 'mobileEdit', ext: ext.edit[j], requires: 'locks,update', urlsrc: urlTemplateMobileEdit}).up(); + if (templatesFolderExtsCache[ext.edit[j]]) { + xmlApp.ele('action', {name: 'editnew', ext: ext.edit[j], requires: 'locks,update', urlsrc: urlTemplateEdit}).up(); + } + } + xmlApp.up(); + } + //end section for MS WOPI connectors + //start section for collabora nexcloud connectors + for(let i = 0; i < exts.length; ++i) { + let ext = exts[i]; + let urlTemplateView = `${templateStart}/${documentTypes[i]}/view?${templateEnd}`; + let urlTemplateEmbedView = `${templateStart}/${documentTypes[i]}/view?embed=1&${templateEnd}`; + let urlTemplateMobileView = `${templateStart}/${documentTypes[i]}/view?mobile=1&${templateEnd}`; + let urlTemplateEdit = `${templateStart}/${documentTypes[i]}/edit?${templateEnd}`; + let urlTemplateMobileEdit = `${templateStart}/${documentTypes[i]}/edit?mobile=1&${templateEnd}`; + let urlTemplateFormSubmit = `${templateStart}/${documentTypes[i]}/edit?formsubmit=1&${templateEnd}`; + let mimeTypesDuplicate = new Set();//to remove duplicates for each editor(allow html for word and excel) + for (let j = 0; j < ext.view.length; ++j) { + let mimeTypes = mimeTypesByExt[ext.view[j]]; + if (mimeTypes) { + mimeTypes.forEach((value) => { + if (mimeTypesDuplicate.has(value)) { + return; + } else { + mimeTypesDuplicate.add(value); + } + let xmlApp = xmlZone.ele('app', {name: value}); + xmlApp.ele('action', {name: 'view', ext: '', default: 'true', urlsrc: urlTemplateView}).up(); + xmlApp.ele('action', {name: 'embedview', ext: '', urlsrc: urlTemplateEmbedView}).up(); + xmlApp.ele('action', {name: 'mobileView', ext: '', urlsrc: urlTemplateMobileView}).up(); + if (ext.targetext) { + let urlConvert = `${templateStart}/convert-and-edit/${ext.view[j]}/${ext.targetext}?${templateEnd}`; + xmlApp.ele('action', {name: 'convert', ext: '', targetext: ext.targetext, requires: 'update', urlsrc: urlConvert}).up(); + } + xmlApp.up(); + }); + } + } + mimeTypesDuplicate.clear(); + for (let j = 0; j < ext.edit.length; ++j) { + let mimeTypes = mimeTypesByExt[ext.edit[j]]; + if (mimeTypes) { + mimeTypes.forEach((value) => { + if (mimeTypesDuplicate.has(value)) { + return; + } else { + mimeTypesDuplicate.add(value); + } + let xmlApp = xmlZone.ele('app', {name: value}); + if (formsExts[ext.edit[j]]) { + xmlApp.ele('action', {name: 'edit', ext: '', default: 'true', requires: 'locks,update', urlsrc: urlTemplateEdit}).up(); + xmlApp.ele('action', {name: 'formsubmit', ext: '', requires: 'locks,update', urlsrc: urlTemplateFormSubmit}).up(); + } else { + xmlApp.ele('action', {name: 'edit', ext: '', default: 'true', requires: 'locks,update', urlsrc: urlTemplateEdit}).up(); + } + xmlApp.ele('action', {name: 'mobileEdit', ext: '', requires: 'locks,update', urlsrc: urlTemplateMobileEdit}).up(); + if (templatesFolderExtsCache[ext.edit[j]]) { + xmlApp.ele('action', {name: 'editnew', ext: '', requires: 'locks,update', urlsrc: urlTemplateEdit}).up(); + } + xmlApp.up(); + }); + } + } + } + let xmlApp = xmlZone.ele('app', {name: 'Capabilities'}); + xmlApp.ele('action', {ext: '', name: 'getinfo', requires: 'locks,update', urlsrc: `${baseUrl}/hosting/capabilities`}).up(); + xmlApp.up(); + //end section for collabora nexcloud connectors + let xmlDiscovery = xmlZone.up(); + if (tenWopiPublicKeyOld && tenWopiPublicKey) { + let exponent = numberToBase64(tenWopiExponent); + let exponentOld = numberToBase64(tenWopiExponentOld); + xmlDiscovery.ele('proof-key', { + oldvalue: tenWopiPublicKeyOld, oldmodulus: tenWopiModulusOld, oldexponent: exponentOld, + value: tenWopiPublicKey, modulus: tenWopiModulus, exponent: exponent + }).up(); + } + xmlDiscovery.up(); + } catch (err) { + ctx.logger.error('wopiDiscovery error:%s', err.stack); + } finally { + res.setHeader('Content-Type', 'text/xml'); + res.send(xml.end()); + ctx.logger.info('wopiDiscovery end'); + } + }); +} +function collaboraCapabilities(req, res) { + return co(function*() { + let output = { + "convert-to": {"available": true, "endpoint":"/lool/convert-to"}, "hasMobileSupport": true, "hasProxyPrefix": false, "hasTemplateSaveAs": false, + "hasTemplateSource": true, "productVersion": commonDefines.buildVersion + }; + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + ctx.logger.info('collaboraCapabilities start'); + } catch (err) { + ctx.logger.error('collaboraCapabilities error:%s', err.stack); + } finally { + utils.fillResponseSimple(res, JSON.stringify(output), "application/json"); + ctx.logger.info('collaboraCapabilities end'); + } + }); +} +function isWopiCallback(url) { + return url && url.startsWith("{"); +} +function isWopiUnlockMarker(url) { + return isWopiCallback(url) && !!JSON.parse(url).unlockId; +} +function isWopiModifiedMarker(url) { + if (isWopiCallback(url)) { + let obj = JSON.parse(url); + return obj.fileInfo && obj.fileInfo.LastModifiedTime + } +} +function getWopiUnlockMarker(wopiParams) { + if (!wopiParams.userAuth || !wopiParams.commonInfo) { + return; + } + return JSON.stringify(Object.assign({unlockId: wopiParams.commonInfo.lockId}, wopiParams.userAuth)); +} +function getWopiModifiedMarker(wopiParams, lastModifiedTime) { + return JSON.stringify(Object.assign({fileInfo: {LastModifiedTime: lastModifiedTime}}, wopiParams.userAuth)); +} +function getFileTypeByInfo(fileInfo) { + let fileType = fileInfo.BaseFileName ? fileInfo.BaseFileName.substr(fileInfo.BaseFileName.lastIndexOf('.') + 1) : ""; + fileType = fileInfo.FileExtension ? fileInfo.FileExtension.substr(1) : fileType; + return fileType.toLowerCase(); +} +async function getWopiFileUrl(ctx, fileInfo, userAuth) { + const tenMaxDownloadBytes = ctx.getCfg('FileConverter.converter.maxDownloadBytes', cfgMaxDownloadBytes); + let url; + let headers = {'X-WOPI-MaxExpectedSize': tenMaxDownloadBytes}; + if (fileInfo?.FileUrl) { + //Requests to the FileUrl can not be signed using proof keys. The FileUrl is used exactly as provided by the host, so it does not necessarily include the access token, which is required to construct the expected proof. + url = fileInfo.FileUrl; + } else if (fileInfo?.TemplateSource) { + url = fileInfo.TemplateSource; + } else if (userAuth) { + url = `${userAuth.wopiSrc}/contents?access_token=${userAuth.access_token}`; + await fillStandardHeaders(ctx, headers, url, userAuth.access_token); + } + ctx.logger.debug('getWopiFileUrl url=%s; headers=%j', url, headers); + return {url, headers}; +} +function isWopiJwtToken(decoded) { + return !!decoded.fileInfo; +} +function setIsShutdown(val) { + shutdownFlag = val; +} +function getLastModifiedTimeFromCallbacks(callbacks) { + for (let i = callbacks.length; i >= 0; --i) { + let callback = callbacks[i]; + let lastModifiedTime = isWopiModifiedMarker(callback); + if (lastModifiedTime) { + return lastModifiedTime; + } + } +} +function isCorrectUserAuth(userAuth) { + return undefined !== userAuth.wopiSrc; +} +function parseWopiCallback(ctx, userAuthStr, opt_url) { + let wopiParams = null; + if (isWopiCallback(userAuthStr)) { + let userAuth = JSON.parse(userAuthStr); + if (!isCorrectUserAuth(userAuth)) { + userAuth = null; + } + let commonInfo = null; + let lastModifiedTime = null; + if (opt_url) { + let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, opt_url, 1); + if (isWopiCallback(commonInfoStr)) { + commonInfo = JSON.parse(commonInfoStr); + if (commonInfo.fileInfo) { + lastModifiedTime = commonInfo.fileInfo.LastModifiedTime; + if (lastModifiedTime) { + let callbacks = sqlBase.UserCallback.prototype.getCallbacks(ctx, opt_url); + lastModifiedTime = getLastModifiedTimeFromCallbacks(callbacks); + } + } else { + commonInfo = null; + } + } + } + wopiParams = {commonInfo: commonInfo, userAuth: userAuth, LastModifiedTime: lastModifiedTime}; + ctx.logger.debug('parseWopiCallback wopiParams:%j', wopiParams); + } + return wopiParams; +} +function checkAndInvalidateCache(ctx, docId, fileInfo) { + return co(function*() { + let res = {success: true, lockId: undefined}; + let selectRes = yield taskResult.select(ctx, docId); + if (selectRes.length > 0) { + let row = selectRes[0]; + if (row.callback) { + let commonInfoStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback, 1); + if (isWopiCallback(commonInfoStr)) { + let commonInfo = JSON.parse(commonInfoStr); + res.lockId = commonInfo.lockId; + ctx.logger.debug('wopiEditor lockId from DB lockId=%s', res.lockId); + let unlockMarkStr = sqlBase.UserCallback.prototype.getCallbackByUserIndex(ctx, row.callback); + ctx.logger.debug('wopiEditor commonInfoStr=%s', commonInfoStr); + ctx.logger.debug('wopiEditor unlockMarkStr=%s', unlockMarkStr); + let hasUnlockMarker = isWopiUnlockMarker(unlockMarkStr); + let isUpdateVersion = commonDefines.FileStatus.UpdateVersion === row.status; + ctx.logger.debug('wopiEditor hasUnlockMarker=%s isUpdateVersion=%s', hasUnlockMarker, isUpdateVersion); + if (hasUnlockMarker || isUpdateVersion) { + let fileInfoVersion = fileInfo.Version; + let cacheVersion = commonInfo.fileInfo.Version; + let fileInfoModified = fileInfo.LastModifiedTime; + let cacheModified = commonInfo.fileInfo.LastModifiedTime; + ctx.logger.debug('wopiEditor version fileInfo=%s; cache=%s', fileInfoVersion, cacheVersion); + ctx.logger.debug('wopiEditor LastModifiedTime fileInfo=%s; cache=%s', fileInfoModified, cacheModified); + if (fileInfoVersion !== cacheVersion || (fileInfoModified !== cacheModified)) { + var mask = new taskResult.TaskResultData(); + mask.tenant = ctx.tenant; + mask.key = docId; + mask.last_open_date = row.last_open_date; + //cleanupRes can be false in case of simultaneous opening. it is OK + let cleanupRes = yield canvasService.cleanupCacheIf(ctx, mask); + ctx.logger.debug('wopiEditor cleanupRes=%s', cleanupRes); + res.lockId = undefined; + } + } + } else { + res.success = false; + ctx.logger.warn('wopiEditor attempt to open not wopi record'); + } + } + } + return res; + }); +} +function parsePutFileResponse(ctx, postRes) { + let body = null + if (postRes.body) { + try { + //collabora nexcloud connector + body = JSON.parse(postRes.body); + } catch (e) { + ctx.logger.debug('wopi PutFile body parse error: %s', e.stack); + } + } + return body; +} +async function checkAndReplaceEmptyFile(ctx, fileInfo, wopiSrc, access_token, access_token_ttl, lang, ui, fileType) { + // TODO: throw error if format not supported? + if (fileInfo.Size === 0 && fileType.length !== 0) { + const tenNewFileTemplate = ctx.getCfg('services.CoAuthoring.server.newFileTemplate', cfgNewFileTemplate); + + //Create new files using Office for the web + const wopiParams = getWopiParams(undefined, fileInfo, wopiSrc, access_token, access_token_ttl); + + if (templatesFolderLocalesCache === null) { + const dirContent = await readdir(`${tenNewFileTemplate}/`, {withFileTypes: true}); + templatesFolderLocalesCache = dirContent.filter(dirObject => dirObject.isDirectory()) + .map(dirObject => dirObject.name); + } + + const localePrefix = lang || ui || 'en'; + let locale = constants.TEMPLATES_FOLDER_LOCALE_COLLISON_MAP[localePrefix] ?? + templatesFolderLocalesCache.find(locale => locale.startsWith(localePrefix)); + if (locale === undefined) { + locale = constants.TEMPLATES_DEFAULT_LOCALE; + } + + const filePath = `${tenNewFileTemplate}/${locale}/new.${fileType}`; + if (!templateFilesSizeCache[filePath]) { + templateFilesSizeCache[filePath] = await lstat(filePath); + } + + const templateFileInfo = templateFilesSizeCache[filePath]; + const templateFileStream = createReadStream(filePath); + let postRes = await putFile(ctx, wopiParams, undefined, templateFileStream, templateFileInfo.size, fileInfo.UserId, false, false, false); + if (postRes) { + //update Size + fileInfo.Size = templateFileInfo.size; + let body = parsePutFileResponse(ctx, postRes); + //collabora nexcloud connector + if (body?.LastModifiedTime) { + //update LastModifiedTime + fileInfo.LastModifiedTime = body.LastModifiedTime; + } + } + } +} +function createDocId(ctx, wopiSrc, mode, fileInfo) { + let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1); + let docId = undefined; + if ('view' !== mode) { + docId = `${fileId}`; + } else { + //todo rename operation requires lock + fileInfo.SupportsRename = false; + //todo change docId to avoid empty cache after editors are gone + if (fileInfo.LastModifiedTime) { + docId = `view.${fileId}.${fileInfo.LastModifiedTime}`; + } else { + docId = `view.${fileId}.${fileInfo.Version}`; + } + } + docId = docId.replace(constants.DOC_ID_REPLACE_REGEX, '_').substring(0, constants.DOC_ID_MAX_LENGTH); + return docId; +} +async function preOpen(ctx, lockId, docId, fileInfo, userAuth, baseUrl, fileType) { + //todo move to lock and common info saving to websocket connection + //save common info + if (undefined === lockId) { + //Use deterministic(not random) lockId to fix issues with forgotten openings due to integrator failures + lockId = docId; + let commonInfo = JSON.stringify({lockId: lockId, fileInfo: fileInfo}); + await canvasService.commandOpenStartPromise(ctx, docId, baseUrl, commonInfo, fileType); + } + //Lock + if ('view' !== userAuth.mode) { + let lockRes = await lock(ctx, 'LOCK', lockId, fileInfo, userAuth); + return !!lockRes; + } + return true; +} +function getEditorHtml(req, res) { + return co(function*() { + let params = {key: undefined, apiQuery: '', fileInfo: {}, userAuth: {}, queryParams: req.query, token: undefined, documentType: undefined, docs_api_config: {}}; + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm); + const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires); + const tenWopiFileInfoBlockList = ctx.getCfg('wopi.fileInfoBlockList', cfgWopiFileInfoBlockList); + + let wopiSrc = req.query['wopisrc']; + let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1); + ctx.setDocId(fileId); + + ctx.logger.info('wopiEditor start'); + ctx.logger.debug(`wopiEditor req.url:%s`, req.url); + ctx.logger.debug(`wopiEditor req.query:%j`, req.query); + ctx.logger.debug(`wopiEditor req.body:%j`, req.body); + params.apiQuery = `?${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(wopiSrc)}`; + params.documentType = req.params.documentType; + let mode = req.params.mode; + let sc = req.query['sc']; + let hostSessionId = req.query['hid']; + let lang = req.query['lang']; + let ui = req.query['ui']; + let access_token = req.body['access_token'] || ""; + let access_token_ttl = parseInt(req.body['access_token_ttl']) || 0; + let docs_api_config = req.body['docs_api_config']; + if (docs_api_config) { + params.docs_api_config = JSON.parse(docs_api_config); + } + + + let fileInfo = params.fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token, sc); + if (!fileInfo) { + params.fileInfo = {}; + return; + } + const fileType = getFileTypeByInfo(fileInfo); + if (!shutdownFlag) { + yield checkAndReplaceEmptyFile(ctx, fileInfo, wopiSrc, access_token, access_token_ttl, lang, ui, fileType); + } + + if (!fileInfo.UserCanWrite) { + mode = 'view'; + } + //docId + let docId = createDocId(ctx, wopiSrc, mode, fileInfo); + ctx.setDocId(fileId); + ctx.logger.debug(`wopiEditor`); + params.key = docId; + let userAuth = params.userAuth = { + wopiSrc: wopiSrc, access_token: access_token, access_token_ttl: access_token_ttl, + hostSessionId: hostSessionId, userSessionId: docId, mode: mode + }; + + //check and invalidate cache + let checkRes = yield checkAndInvalidateCache(ctx, docId, fileInfo); + if (!checkRes.success) { + params.fileInfo = {}; + return; + } + if (!shutdownFlag) { + let preOpenRes = yield preOpen(ctx, checkRes.lockId, docId, fileInfo, userAuth, utils.getBaseUrlByRequest(ctx, req), fileType); + if (!preOpenRes) { + params.fileInfo = {}; + return; + } + } + + tenWopiFileInfoBlockList.forEach((item) => { + delete params.fileInfo[item]; + }); + + if (tenTokenEnableBrowser) { + let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires}; + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser); + params.token = jwt.sign(params, secret, options); + } + } catch (err) { + ctx.logger.error('wopiEditor error:%s', err.stack); + params.fileInfo = {}; + } finally { + ctx.logger.debug('wopiEditor render params=%j', params); + try { + res.render("editor-wopi", params); + } catch (err) { + ctx.logger.error('wopiEditor error:%s', err.stack); + res.sendStatus(400); + } + ctx.logger.info('wopiEditor end'); + } + }); +} +function getConverterHtml(req, res) { + return co(function*() { + let params = {statusHandler: undefined}; + let ctx = new operationContext.Context(); + try { + ctx.initFromRequest(req); + yield ctx.initTenantCache(); + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm); + const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires); + const tenWopiHost = ctx.getCfg('wopi.host', cfgWopiHost); + + let wopiSrc = req.query['wopisrc']; + let fileId = wopiSrc.substring(wopiSrc.lastIndexOf('/') + 1); + ctx.setDocId(fileId); + ctx.logger.info('convert-and-edit start'); + + let access_token = req.body['access_token'] || ""; + let access_token_ttl = parseInt(req.body['access_token_ttl']) || 0; + let ext = req.params.ext; + let targetext = req.params.targetext; + + if (!(wopiSrc && access_token && access_token_ttl && ext && targetext)) { + ctx.logger.debug('convert-and-edit invalid params: WOPISrc=%s; access_token=%s; access_token_ttl=%s; ext=%s; targetext=%s', wopiSrc, access_token, access_token_ttl, ext, targetext); + return; + } + + let fileInfo = yield checkFileInfo(ctx, wopiSrc, access_token); + if (!fileInfo) { + ctx.logger.info('convert-and-edit checkFileInfo error'); + return; + } + + let wopiParams = getWopiParams(undefined, fileInfo, wopiSrc, access_token, access_token_ttl); + + let docId = yield converterService.convertAndEdit(ctx, wopiParams, ext, targetext); + if (docId) { + let baseUrl = tenWopiHost || utils.getBaseUrlByRequest(ctx, req); + params.statusHandler = `${baseUrl}/hosting/wopi/convert-and-edit-handler`; + params.statusHandler += `?${constants.SHARD_KEY_WOPI_NAME}=${encodeURIComponent(wopiSrc)}&access_token=${encodeURIComponent(access_token)}`; + params.statusHandler += `&targetext=${encodeURIComponent(targetext)}&docId=${encodeURIComponent(docId)}`; + if (tenTokenEnableBrowser) { + let tokenData = {docId: docId}; + let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires}; + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser); + let token = jwt.sign(tokenData, secret, options); + + params.statusHandler += `&token=${encodeURIComponent(token)}`; + } + } + } catch (err) { + ctx.logger.error('convert-and-edit error:%s', err.stack); + } finally { + ctx.logger.debug('convert-and-edit render params=%j', params); + try { + res.render("convert-and-edit-wopi", params); + } catch (err) { + ctx.logger.error('convert-and-edit error:%s', err.stack); + res.sendStatus(400); + } + ctx.logger.info('convert-and-edit end'); + } + }); +} +function putFile(ctx, wopiParams, data, dataStream, dataSize, userLastChangeId, isModifiedByUser, isAutosave, isExitSave) { + return co(function* () { + let postRes = null; + try { + ctx.logger.info('wopi PutFile start'); + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + + if (!wopiParams.userAuth || !wopiParams.commonInfo) { + return postRes; + } + let fileInfo = wopiParams.commonInfo.fileInfo; + let userAuth = wopiParams.userAuth; + let uri = `${userAuth.wopiSrc}/contents?access_token=${userAuth.access_token}`; + let filterStatus = yield checkIpFilter(ctx, uri); + if (0 !== filterStatus) { + return postRes; + } + + //collabora nexcloud connector sets only UserCanWrite=true + if (fileInfo && (fileInfo.SupportsUpdate || fileInfo.UserCanWrite)) { + let commonInfo = wopiParams.commonInfo; + //todo add all the users who contributed changes to the document in this PutFile request to X-WOPI-Editors + let headers = {'X-WOPI-Override': 'PUT', 'X-WOPI-Lock': commonInfo.lockId, 'X-WOPI-Editors': userLastChangeId}; + yield fillStandardHeaders(ctx, headers, uri, userAuth.access_token); + headers['X-LOOL-WOPI-IsModifiedByUser'] = isModifiedByUser; + headers['X-LOOL-WOPI-IsAutosave'] = isAutosave; + headers['X-LOOL-WOPI-IsExitSave'] = isExitSave; + if (wopiParams.LastModifiedTime) { + //collabora nexcloud connector + headers['X-LOOL-WOPI-Timestamp'] = wopiParams.LastModifiedTime; + } + headers['Content-Type'] = mime.getType(getFileTypeByInfo(fileInfo)); + + ctx.logger.debug('wopi PutFile request uri=%s headers=%j', uri, headers); + //isInJwtToken is true because it passed checkIpFilter for wopi + let isInJwtToken = true; + postRes = yield utils.postRequestPromise(ctx, uri, data, dataStream, dataSize, tenCallbackRequestTimeout, undefined, isInJwtToken, headers); + ctx.logger.debug('wopi PutFile response headers=%j', postRes.response.headers); + ctx.logger.debug('wopi PutFile response body:%s', postRes.body); + } else { + ctx.logger.warn('wopi SupportsUpdate = false or UserCanWrite = false'); + } + } catch (err) { + ctx.logger.error('wopi error PutFile:%s', err.stack); + } finally { + ctx.logger.info('wopi PutFile end'); + } + return postRes; + }); +} +function putRelativeFile(ctx, wopiSrc, access_token, data, dataStream, dataSize, suggestedExt, suggestedTarget, isFileConversion) { + return co(function* () { + let res = undefined; + try { + ctx.logger.info('wopi putRelativeFile start'); + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + + let uri = `${wopiSrc}?access_token=${access_token}`; + let filterStatus = yield checkIpFilter(ctx, uri); + if (0 !== filterStatus) { + return res; + } + + let headers = {'X-WOPI-Override': 'PUT_RELATIVE', 'X-WOPI-SuggestedTarget': utf7.encode(suggestedTarget || suggestedExt)}; + if (isFileConversion) { + headers['X-WOPI-FileConversion'] = isFileConversion; + } + yield fillStandardHeaders(ctx, headers, uri, access_token); + headers['Content-Type'] = mime.getType(suggestedExt); + + ctx.logger.debug('wopi putRelativeFile request uri=%s headers=%j', uri, headers); + //isInJwtToken is true because it passed checkIpFilter for wopi + let isInJwtToken = true; + let postRes = yield utils.postRequestPromise(ctx, uri, data, dataStream, dataSize, tenCallbackRequestTimeout, undefined, isInJwtToken, headers); + ctx.logger.debug('wopi putRelativeFile response headers=%j', postRes.response.headers); + ctx.logger.debug('wopi putRelativeFile response body:%s', postRes.body); + res = JSON.parse(postRes.body); + } catch (err) { + ctx.logger.error('wopi error putRelativeFile:%s', err.stack); + } finally { + ctx.logger.info('wopi putRelativeFile end'); + } + return res; + }); +} +function renameFile(ctx, wopiParams, name) { + return co(function* () { + let res = undefined; + try { + ctx.logger.info('wopi RenameFile start'); + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + + if (!wopiParams.userAuth || !wopiParams.commonInfo) { + return res; + } + let fileInfo = wopiParams.commonInfo.fileInfo; + let userAuth = wopiParams.userAuth; + let uri = `${userAuth.wopiSrc}?access_token=${userAuth.access_token}`; + let filterStatus = yield checkIpFilter(ctx, uri); + if (0 !== filterStatus) { + return res; + } + + if (fileInfo && fileInfo.SupportsRename) { + let fileNameMaxLength = fileInfo.FileNameMaxLength || 255; + name = name.substring(0, fileNameMaxLength); + let commonInfo = wopiParams.commonInfo; + + let headers = {'X-WOPI-Override': 'RENAME_FILE', 'X-WOPI-Lock': commonInfo.lockId, 'X-WOPI-RequestedName': utf7.encode(name)}; + yield fillStandardHeaders(ctx, headers, uri, userAuth.access_token); + + ctx.logger.debug('wopi RenameFile request uri=%s headers=%j', uri, headers); + //isInJwtToken is true because it passed checkIpFilter for wopi + let isInJwtToken = true; + let postRes = yield utils.postRequestPromise(ctx, uri, undefined, undefined, undefined, tenCallbackRequestTimeout, undefined, isInJwtToken, headers); + ctx.logger.debug('wopi RenameFile response headers=%j body=%s', postRes.response.headers, postRes.body); + if (postRes.body) { + res = JSON.parse(postRes.body); + } else { + //sharepoint send empty body(2016 allways, 2019 with same name) + res = {"Name": name}; + } + } else { + ctx.logger.info('wopi SupportsRename = false'); + } + } catch (err) { + ctx.logger.error('wopi error RenameFile:%s', err.stack); + } finally { + ctx.logger.info('wopi RenameFile end'); + } + return res; + }); +} + +async function refreshFile(ctx, wopiParams, baseUrl) { + let res = {}; + try { + ctx.logger.info('wopi RefreshFile start'); + let userAuth = wopiParams.userAuth; + if (!userAuth) { + return; + } + const tenTokenEnableBrowser = ctx.getCfg('services.CoAuthoring.token.enable.browser', cfgTokenEnableBrowser); + const tenTokenOutboxAlgorithm = ctx.getCfg('services.CoAuthoring.token.outbox.algorithm', cfgTokenOutboxAlgorithm); + const tenTokenOutboxExpires = ctx.getCfg('services.CoAuthoring.token.outbox.expires', cfgTokenOutboxExpires); + + const fileInfo = await checkFileInfo(ctx, userAuth.wopiSrc, userAuth.access_token); + const fileType = getFileTypeByInfo(fileInfo); + const docId = createDocId(ctx, userAuth.wopiSrc, userAuth.mode, res.fileInfo); + res.key = docId; + res.userAuth = userAuth; + res.fileInfo = fileInfo; + res.queryParams = undefined; + if (tenTokenEnableBrowser) { + let options = {algorithm: tenTokenOutboxAlgorithm, expiresIn: tenTokenOutboxExpires}; + let secret = await tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Browser); + res.token = jwt.sign(res, secret, options); + } + let checkRes = await checkAndInvalidateCache(ctx, docId, fileInfo); + if (!checkRes.success) { + res = {}; + return; + } + let preOpenRes = await preOpen(ctx, checkRes.lockId, docId, fileInfo, userAuth, baseUrl, fileType); + if (!preOpenRes) { + res = {}; + } + } catch (err) { + ctx.logger.error('wopi error RefreshFile:%s', err.stack); + } finally { + ctx.logger.info('wopi RefreshFile end'); + } + return res; +} +function checkFileInfo(ctx, wopiSrc, access_token, opt_sc) { + return co(function* () { + let fileInfo = undefined; + try { + ctx.logger.info('wopi checkFileInfo start'); + const tenDownloadTimeout = ctx.getCfg('FileConverter.converter.downloadTimeout', cfgDownloadTimeout); + + let uri = `${encodeURI(wopiSrc)}?access_token=${encodeURIComponent(access_token)}`; + let filterStatus = yield checkIpFilter(ctx, uri); + if (0 !== filterStatus) { + return fileInfo; + } + let headers = {}; + if (opt_sc) { + headers['X-WOPI-SessionContext'] = opt_sc; + } + yield fillStandardHeaders(ctx, headers, uri, access_token); + ctx.logger.debug('wopi checkFileInfo request uri=%s headers=%j', uri, headers); + //isInJwtToken is true because it passed checkIpFilter for wopi + let isInJwtToken = true; + let getRes = yield utils.downloadUrlPromise(ctx, uri, tenDownloadTimeout, undefined, undefined, isInJwtToken, headers); + ctx.logger.debug(`wopi checkFileInfo headers=%j body=%s`, getRes.response.headers, getRes.body); + fileInfo = JSON.parse(getRes.body); + } catch (err) { + ctx.logger.error('wopi error checkFileInfo:%s', err.stack); + } finally { + ctx.logger.info('wopi checkFileInfo end'); + } + return fileInfo; + }); +} +function lock(ctx, command, lockId, fileInfo, userAuth) { + return co(function* () { + let res = true; + try { + ctx.logger.info('wopi %s start', command); + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + + if (fileInfo && fileInfo.SupportsLocks) { + if (!userAuth) { + return false; + } + let wopiSrc = userAuth.wopiSrc; + let access_token = userAuth.access_token; + let uri = `${wopiSrc}?access_token=${access_token}`; + let filterStatus = yield checkIpFilter(ctx, uri); + if (0 !== filterStatus) { + return false; + } + + let headers = {"X-WOPI-Override": command, "X-WOPI-Lock": lockId}; + yield fillStandardHeaders(ctx, headers, uri, access_token); + ctx.logger.debug('wopi %s request uri=%s headers=%j', command, uri, headers); + //isInJwtToken is true because it passed checkIpFilter for wopi + let isInJwtToken = true; + let postRes = yield utils.postRequestPromise(ctx, uri, undefined, undefined, undefined, tenCallbackRequestTimeout, undefined, isInJwtToken, headers); + ctx.logger.debug('wopi %s response headers=%j', command, postRes.response.headers); + } else { + ctx.logger.info('wopi %s SupportsLocks = false', command); + } + } catch (err) { + res = false; + ctx.logger.error('wopi error %s:%s', command, err.stack); + } finally { + ctx.logger.info('wopi %s end', command); + } + return res; + }); +} +async function unlock(ctx, wopiParams) { + let res = false; + try { + ctx.logger.info('wopi Unlock start'); + const tenCallbackRequestTimeout = ctx.getCfg('services.CoAuthoring.server.callbackRequestTimeout', cfgCallbackRequestTimeout); + + if (!wopiParams.userAuth || !wopiParams.commonInfo) { + return; + } + let fileInfo = wopiParams.commonInfo.fileInfo; + if (fileInfo && fileInfo.SupportsLocks) { + let wopiSrc = wopiParams.userAuth.wopiSrc; + let lockId = wopiParams.commonInfo.lockId; + let access_token = wopiParams.userAuth.access_token; + let uri = `${wopiSrc}?access_token=${access_token}`; + let filterStatus = await checkIpFilter(ctx, uri); + if (0 !== filterStatus) { + return; + } + + let headers = {"X-WOPI-Override": "UNLOCK", "X-WOPI-Lock": lockId}; + await fillStandardHeaders(ctx, headers, uri, access_token); + ctx.logger.debug('wopi Unlock request uri=%s headers=%j', uri, headers); + //isInJwtToken is true because it passed checkIpFilter for wopi + let isInJwtToken = true; + let postRes = await utils.postRequestPromise(ctx, uri, undefined, undefined, undefined, tenCallbackRequestTimeout, undefined, isInJwtToken, headers); + ctx.logger.debug('wopi Unlock response headers=%j', postRes.response.headers); + } else { + ctx.logger.info('wopi SupportsLocks = false'); + } + res = true; + } catch (err) { + ctx.logger.error('wopi error Unlock:%s', err.stack); + } finally { + ctx.logger.info('wopi Unlock end'); + } + return res; +} +function generateProofBuffer(url, accessToken, timeStamp) { + const accessTokenBytes = Buffer.from(accessToken, 'utf8'); + const urlBytes = Buffer.from(url.toUpperCase(), 'utf8'); + + let offset = 0; + let buffer = Buffer.alloc(4 + accessTokenBytes.length + 4 + urlBytes.length + 4 + 8); + buffer.writeUInt32BE(accessTokenBytes.length, offset); + offset += 4; + accessTokenBytes.copy(buffer, offset, 0, accessTokenBytes.length); + offset += accessTokenBytes.length; + buffer.writeUInt32BE(urlBytes.length, offset); + offset += 4; + urlBytes.copy(buffer, offset, 0, urlBytes.length); + offset += urlBytes.length; + buffer.writeUInt32BE(8, offset); + offset += 4; + buffer.writeBigUInt64BE(timeStamp, offset); + return buffer; +} + +async function generateProofSign(url, accessToken, timeStamp, privateKey) { + let data = generateProofBuffer(url, accessToken, timeStamp); + let sign = await cryptoSign('RSA-SHA256', data, privateKey); + return sign.toString('base64'); +} + +function numberToBase64(val) { + // Convert to hexadecimal + let hexString = val.toString(16); + //Ensure the hexadecimal string has an even length + if (hexString.length % 2 !== 0) { + hexString = '0' + hexString; + } + //Convert the hexadecimal string to a buffer + const buffer = Buffer.from(hexString, 'hex'); + return buffer.toString('base64'); +} + +async function fillStandardHeaders(ctx, headers, url, access_token) { + let timeStamp = utils.getDateTimeTicks(new Date()); + const tenWopiPrivateKey = ctx.getCfg('wopi.privateKey', cfgWopiPrivateKey); + const tenWopiPrivateKeyOld = ctx.getCfg('wopi.privateKeyOld', cfgWopiPrivateKeyOld); + if (tenWopiPrivateKey && tenWopiPrivateKeyOld) { + headers['X-WOPI-Proof'] = await generateProofSign(url, access_token, timeStamp, tenWopiPrivateKey); + headers['X-WOPI-ProofOld'] = await generateProofSign(url, access_token, timeStamp, tenWopiPrivateKeyOld); + } + headers['X-WOPI-TimeStamp'] = timeStamp; + headers['X-WOPI-ClientVersion'] = commonDefines.buildVersion + '.' + commonDefines.buildNumber; + // todo + // headers['X-WOPI-CorrelationId '] = ""; + // headers['X-WOPI-SessionId'] = ""; + //remove redundant header https://learn.microsoft.com/en-us/microsoft-365/cloud-storage-partner-program/rest/common-headers#request-headers + // headers['Authorization'] = `Bearer ${access_token}`; +} + +function checkIpFilter(ctx, uri){ + return co(function* () { + let urlParsed = new URL(uri); + let filterStatus = yield* utils.checkHostFilter(ctx, urlParsed.hostname); + if (0 !== filterStatus) { + ctx.logger.warn('wopi checkIpFilter error: url = %s', uri); + } + return filterStatus; + }); +} +function getWopiParams(lockId, fileInfo, wopiSrc, access_token, access_token_ttl) { + let commonInfo = {lockId: lockId, fileInfo: fileInfo}; + let userAuth = { + wopiSrc: wopiSrc, access_token: access_token, access_token_ttl: access_token_ttl, + hostSessionId: null, userSessionId: null, mode: null + }; + return {commonInfo: commonInfo, userAuth: userAuth, LastModifiedTime: null}; +} + +async function dummyCheckFileInfo(req, res) { + if (true) { + //static output for performance reason + res.json({ + BaseFileName: "sample.docx", + OwnerId: "userId", + Size: 100,//no need to set actual size for test + UserId: "userId",//test ignores + UserFriendlyName: "user", + Version: 0, + UserCanWrite: true, + SupportsGetLock: true, + SupportsLocks: true, + SupportsUpdate: true, + }); + } else { + let fileInfo; + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + try { + await ctx.initTenantCache(); + const tenWopiDummySampleFilePath = ctx.getCfg('wopi.dummy.sampleFilePath', cfgWopiDummySampleFilePath); + let access_token = req.query['access_token']; + ctx.logger.debug('dummyCheckFileInfo access_token:%s', access_token); + let sampleFileStat = await stat(tenWopiDummySampleFilePath); + + fileInfo = JSON.parse(Buffer.from(access_token, 'base64').toString('ascii')); + fileInfo.BaseFileName = path.basename(tenWopiDummySampleFilePath); + fileInfo.Size = sampleFileStat.size; + } catch (err) { + ctx.logger.error('dummyCheckFileInfo error:%s', err.stack); + } finally { + if (fileInfo) { + res.json(fileInfo); + } else { + res.sendStatus(400) + } + } + } +} + +async function dummyGetFile(req, res) { + let ctx = new operationContext.Context(); + ctx.initFromRequest(req); + try { + await ctx.initTenantCache(); + + const tenWopiDummySampleFilePath = ctx.getCfg('wopi.dummy.sampleFilePath', cfgWopiDummySampleFilePath); + let sampleFileStat = await stat(tenWopiDummySampleFilePath); + res.setHeader('Content-Length', sampleFileStat.size); + res.setHeader('Content-Type', mime.getType(tenWopiDummySampleFilePath)); + + await pipeline( + createReadStream(tenWopiDummySampleFilePath), + res, + ); + } catch (err) { + if (err.code === "ERR_STREAM_PREMATURE_CLOSE") { + //xhr.abort case + ctx.logger.debug('dummyGetFile error: %s', err.stack); + } else { + ctx.logger.error('dummyGetFile error:%s', err.stack); + } + } finally { + if (!res.headersSent) { + res.sendStatus(400); + } + } +} +function dummyOk(req, res) { + res.sendStatus(200); +} + +exports.checkIpFilter = checkIpFilter; +exports.discovery = discovery; +exports.collaboraCapabilities = collaboraCapabilities; +exports.parseWopiCallback = parseWopiCallback; +exports.getEditorHtml = getEditorHtml; +exports.getConverterHtml = getConverterHtml; +exports.putFile = putFile; +exports.parsePutFileResponse = parsePutFileResponse; +exports.putRelativeFile = putRelativeFile; +exports.renameFile = renameFile; +exports.refreshFile = refreshFile; +exports.lock = lock; +exports.unlock = unlock; +exports.fillStandardHeaders = fillStandardHeaders; +exports.getWopiUnlockMarker = getWopiUnlockMarker; +exports.getWopiModifiedMarker = getWopiModifiedMarker; +exports.getFileTypeByInfo = getFileTypeByInfo; +exports.getWopiFileUrl = getWopiFileUrl; +exports.isWopiJwtToken = isWopiJwtToken; +exports.setIsShutdown = setIsShutdown; +exports.dummyCheckFileInfo = dummyCheckFileInfo; +exports.dummyGetFile = dummyGetFile; +exports.dummyOk = dummyOk; diff --git a/FileConverter/bin/DoctRenderer.config b/FileConverter/bin/DoctRenderer.config deleted file mode 100644 index fc465ac5e9..0000000000 --- a/FileConverter/bin/DoctRenderer.config +++ /dev/null @@ -1,19 +0,0 @@ - - ../../../sdkjs/common/Native/native.js - ../../../sdkjs/common/Native/jquery_native.js - ../../../web-apps/vendor/xregexp/xregexp-all-min.js - ../../../sdkjs/common/AllFonts.js - ../../../web-apps/vendor/jquery/jquery.min.js - - ../../../sdkjs/word/sdk-all-min.js - ../../../sdkjs/word/sdk-all.js - - - ../../../sdkjs/slide/sdk-all-min.js - ../../../sdkjs/slide/sdk-all.js - - - ../../../sdkjs/cell/sdk-all-min.js - ../../../sdkjs/cell/sdk-all.js - - \ No newline at end of file diff --git a/FileConverter/npm-shrinkwrap.json b/FileConverter/npm-shrinkwrap.json new file mode 100644 index 0000000000..57a5f4e6c7 --- /dev/null +++ b/FileConverter/npm-shrinkwrap.json @@ -0,0 +1,101 @@ +{ + "name": "fileconverter", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@expo/spawn-async": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz", + "integrity": "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==", + "requires": { + "cross-spawn": "^7.0.3" + } + }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "config": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/config/-/config-2.0.1.tgz", + "integrity": "sha512-aTaviJnC8ZjQYx8kQf4u6tWqIxWolyQQ3LqXgnCLAsIb78JrUshHG0YuzIarzTaVVe1Pazms3TXImfYra8UsyQ==", + "requires": { + "json5": "^1.0.1" + } + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "invert-kv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-3.0.1.tgz", + "integrity": "sha512-CYdFeFexxhv/Bcny+Q0BfOV+ltRlJcd4BBZBYFX/O0u4npJrgZtIcjokegtiSMAvlMTJ+Koq0GBCc//3bueQxw==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "requires": { + "minimist": "^1.2.0" + } + }, + "lcid": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-3.1.1.tgz", + "integrity": "sha512-M6T051+5QCGLBQb8id3hdvIW8+zeFV2FyBGFS9IEK5H9Wt4MueD4bW1eWikpHgZp+5xR3l5c8pZUkQsIA0BFZg==", + "requires": { + "invert-kv": "^3.0.0" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + } + } +} diff --git a/FileConverter/package.json b/FileConverter/package.json index e21d1b7728..8c53cceeb0 100644 --- a/FileConverter/package.json +++ b/FileConverter/package.json @@ -1,10 +1,22 @@ { "name": "fileconverter", "version": "1.0.1", - "homepage": "http://www.onlyoffice.com", + "homepage": "https://www.onlyoffice.com", "private": true, + "bin": "sources/convertermaster.js", "dependencies": { - "co": "^4.6.0", - "config": "^1.21.0" + "@expo/spawn-async": "1.7.2", + "bytes": "3.0.0", + "co": "4.6.0", + "config": "2.0.1", + "lcid": "3.1.1" + }, + "pkg": { + "scripts": [ + "../Common/sources/storage-fs.js", + "../Common/sources/storage-s3.js", + "../DocService/sources/editorDataMemory.js", + "../DocService/sources/editorDataRedis.js" + ] } } diff --git a/FileConverter/sources/converter.js b/FileConverter/sources/converter.js index ba1aedf5b6..8c8a01339b 100644 --- a/FileConverter/sources/converter.js +++ b/FileConverter/sources/converter.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -35,30 +35,48 @@ var os = require('os'); var path = require('path'); var fs = require('fs'); var url = require('url'); -var childProcess = require('child_process'); var co = require('co'); var config = require('config'); -var configConverter = config.get('FileConverter.converter'); +var spawnAsync = require('@expo/spawn-async'); +const bytes = require('bytes'); +const lcid = require('lcid'); +const ms = require('ms'); var commonDefines = require('./../../Common/sources/commondefines'); var storage = require('./../../Common/sources/storage-base'); var utils = require('./../../Common/sources/utils'); -var logger = require('./../../Common/sources/logger'); var constants = require('./../../Common/sources/constants'); -var baseConnector = require('./../../DocService/sources/baseConnector'); +var baseConnector = require('../../DocService/sources/databaseConnectors/baseConnector'); +const wopiClient = require('./../../DocService/sources/wopiClient'); +const taskResult = require('./../../DocService/sources/taskresult'); var statsDClient = require('./../../Common/sources/statsdclient'); var queueService = require('./../../Common/sources/taskqueueRabbitMQ'); +const formatChecker = require('./../../Common/sources/formatchecker'); +const operationContext = require('./../../Common/sources/operationContext'); +const tenantManager = require('./../../Common/sources/tenantManager'); -var cfgDownloadMaxBytes = configConverter.has('maxDownloadBytes') ? configConverter.get('maxDownloadBytes') : 100000000; -var cfgDownloadTimeout = configConverter.has('downloadTimeout') ? configConverter.get('downloadTimeout') : 60; -var cfgDownloadAttemptMaxCount = configConverter.has('downloadAttemptMaxCount') ? configConverter.get('downloadAttemptMaxCount') : 3; -var cfgDownloadAttemptDelay = configConverter.has('downloadAttemptDelay') ? configConverter.get('downloadAttemptDelay') : 1000; -var cfgFontDir = configConverter.get('fontDir'); -var cfgPresentationThemesDir = configConverter.get('presentationThemesDir'); -var cfgFilePath = configConverter.get('filePath'); -var cfgArgs = configConverter.get('args'); -var cfgErrorFiles = configConverter.get('errorfiles'); -var cfgTokenEnableRequestOutbox = config.get('services.CoAuthoring.token.enable.request.outbox'); +const cfgMaxDownloadBytes = config.get('FileConverter.converter.maxDownloadBytes'); +const cfgDownloadTimeout = config.get('FileConverter.converter.downloadTimeout'); +const cfgDownloadAttemptMaxCount = config.get('FileConverter.converter.downloadAttemptMaxCount'); +const cfgDownloadAttemptDelay = config.get('FileConverter.converter.downloadAttemptDelay'); +const cfgFontDir = config.get('FileConverter.converter.fontDir'); +const cfgPresentationThemesDir = config.get('FileConverter.converter.presentationThemesDir'); +const cfgX2tPath = config.get('FileConverter.converter.x2tPath'); +const cfgDocbuilderPath = config.get('FileConverter.converter.docbuilderPath'); +const cfgArgs = config.get('FileConverter.converter.args'); +const cfgSpawnOptions = config.get('FileConverter.converter.spawnOptions'); +const cfgErrorFiles = config.get('FileConverter.converter.errorfiles'); +const cfgInputLimits = config.get('FileConverter.converter.inputLimits'); +const cfgStreamWriterBufferSize = config.get('FileConverter.converter.streamWriterBufferSize'); +//cfgMaxRequestChanges was obtained as a result of the test: 84408 changes - 5,16 MB +const cfgMaxRequestChanges = config.get('services.CoAuthoring.server.maxRequestChanges'); +const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles'); +const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname'); +const cfgNewFileTemplate = config.get('services.CoAuthoring.server.newFileTemplate'); +const cfgEditor = config.get('services.CoAuthoring.editor'); +const cfgRequesFilteringAgent = config.get('services.CoAuthoring.request-filtering-agent'); +const cfgExternalRequestDirectIfIn = config.get('externalRequest.directIfIn'); +const cfgExternalRequestAction = config.get('externalRequest.action'); //windows limit 512(2048) https://msdn.microsoft.com/en-us/library/6e3b887c.aspx //Ubuntu 14.04 limit 4096 http://underyx.me/2015/05/18/raising-the-maximum-number-of-file-descriptors.html @@ -67,47 +85,70 @@ var MAX_OPEN_FILES = 200; var TEMP_PREFIX = 'ASC_CONVERT'; var queue = null; var clientStatsD = statsDClient.getClient(); -var exitCodesReturn = [constants.CONVERT_NEED_PARAMS, constants.CONVERT_CORRUPTED, constants.CONVERT_DRM, - constants.CONVERT_PASSWORD]; -var exitCodesMinorError = [constants.CONVERT_NEED_PARAMS, constants.CONVERT_DRM, constants.CONVERT_PASSWORD]; +var exitCodesReturn = [constants.CONVERT_PARAMS, constants.CONVERT_NEED_PARAMS, constants.CONVERT_CORRUPTED, + constants.CONVERT_DRM, constants.CONVERT_DRM_UNSUPPORTED, constants.CONVERT_PASSWORD, constants.CONVERT_LIMITS, + constants.CONVERT_DETECT]; +var exitCodesMinorError = [constants.CONVERT_NEED_PARAMS, constants.CONVERT_DRM, constants.CONVERT_DRM_UNSUPPORTED, constants.CONVERT_PASSWORD]; var exitCodesUpload = [constants.NO_ERROR, constants.CONVERT_CORRUPTED, constants.CONVERT_NEED_PARAMS, - constants.CONVERT_DRM]; + constants.CONVERT_DRM, constants.CONVERT_DRM_UNSUPPORTED]; +var exitCodesCopyOrigin = [constants.CONVERT_NEED_PARAMS, constants.CONVERT_DRM]; +let inputLimitsXmlCache; -function TaskQueueDataConvert(task) { +function TaskQueueDataConvert(ctx, task) { var cmd = task.getCmd(); - this.key = cmd.savekey ? cmd.savekey : cmd.id; + this.key = cmd.getDocId(); + if (cmd.getSaveKey()) { + this.key += cmd.getSaveKey(); + } this.fileFrom = null; this.fileTo = null; - this.formatTo = cmd.outputformat; - this.csvTxtEncoding = cmd.codepage; - this.csvDelimiter = cmd.delimiter; + this.title = cmd.getTitle(); + if(constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA !== cmd.getOutputFormat()){ + this.formatTo = cmd.getOutputFormat(); + } else { + this.formatTo = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF; + this.isPDFA = true; + } + this.csvTxtEncoding = cmd.getCodepage(); + this.csvDelimiter = cmd.getDelimiter(); + this.csvDelimiterChar = cmd.getDelimiterChar(); this.paid = task.getPaid(); this.embeddedFonts = cmd.embeddedfonts; this.fromChanges = task.getFromChanges(); //todo - if (cfgFontDir) { - this.fontDir = path.resolve(cfgFontDir); + const tenFontDir = ctx.getCfg('FileConverter.converter.fontDir', cfgFontDir); + if (tenFontDir) { + this.fontDir = path.resolve(tenFontDir); } else { - this.fontDir = cfgFontDir; + this.fontDir = null; } - this.themeDir = path.resolve(cfgPresentationThemesDir); + const tenPresentationThemesDir = ctx.getCfg('FileConverter.converter.presentationThemesDir', cfgPresentationThemesDir); + this.themeDir = path.resolve(tenPresentationThemesDir); this.mailMergeSend = cmd.mailmergesend; this.thumbnail = cmd.thumbnail; - this.doctParams = cmd.getDoctParams(); + this.textParams = cmd.getTextParams(); + this.jsonParams = JSON.stringify(cmd.getJsonParams()); + this.lcid = cmd.getLCID(); this.password = cmd.getPassword(); + this.savePassword = cmd.getSavePassword(); + this.noBase64 = cmd.getNoBase64(); + this.convertToOrigin = cmd.getConvertToOrigin(); this.timestamp = new Date(); } TaskQueueDataConvert.prototype = { - serialize: function(fsPath) { - var xml = '\ufeff'; + serialize: function(ctx, fsPath) { + let xml = '\ufeff'; xml += ' 0) { + xml += this.serializeXmlProp('allowList', allowList.join(';')); + } + xml += this.serializeXmlProp('allowNetworkRequest', allowNetworkRequest); + xml += this.serializeXmlProp('allowPrivateIP', allowPrivateIP); + if (proxyUrl) { + xml += this.serializeXmlProp('proxy', proxyUrl); + } + if (proxyUser) { + let user = proxyUser.username; + let pass = proxyUser.password; + xml += this.serializeXmlProp('proxyUser', `${user}:${pass}`); + } + let proxyHeadersStr= []; + for (let name in proxyHeaders) { + proxyHeadersStr.push(`${name}:${proxyHeaders[name]}`); + } + if (proxyHeadersStr.length > 0) { + xml += this.serializeXmlProp('proxyHeader', proxyHeadersStr.join(';')); + } + xml += ''; + return xml; + }, serializeMailMerge: function(data) { var xml = ''; xml += this.serializeXmlProp('from', data.getFrom()); @@ -151,8 +260,42 @@ TaskQueueDataConvert.prototype = { xml += ''; return xml; }, + serializeTextParams: function(data) { + var xml = ''; + xml += this.serializeXmlProp('m_nTextAssociationType', data.getAssociation()); + xml += ''; + return xml; + }, + serializeLimit: function(ctx) { + if (!inputLimitsXmlCache) { + var xml = ''; + const tenInputLimits = ctx.getCfg('FileConverter.converter.inputLimits', cfgInputLimits); + for (let i = 0; i < tenInputLimits.length; ++i) { + let limit = tenInputLimits[i]; + if (limit.type && limit.zip) { + xml += ''; xml += utils.encodeXml(value.toString()); @@ -161,6 +304,15 @@ TaskQueueDataConvert.prototype = { xml += '<' + name + ' xsi:nil="true" />'; } return xml; + }, + serializeXmlAttr: function(name, value) { + var xml = ''; + if (null != value) { + xml += ' ' + name + '=\"'; + xml += utils.encodeXml(value.toString()); + xml += '\"'; + } + return xml; } }; @@ -169,7 +321,7 @@ function getTempDir() { var now = new Date(); var newTemp; while (!newTemp || fs.existsSync(newTemp)) { - var newName = [TEMP_PREFIX, now.getYear(), now.getMonth(), now.getDate(), + var newName = [TEMP_PREFIX, now.getFullYear(), now.getMonth(), now.getDate(), '-', (Math.random() * 0x100000000 + 1).toString(36) ].join(''); newTemp = path.join(tempDir, newName); @@ -181,63 +333,120 @@ function getTempDir() { fs.mkdirSync(resultDir); return {temp: newTemp, source: sourceDir, result: resultDir}; } -function* downloadFile(docId, uri, fileFrom) { - var res = false; +function* isUselessConvertion(ctx, task, cmd) { + if (task.getFromChanges() && 'sfc' === cmd.getCommand()) { + let selectRes = yield taskResult.select(ctx, cmd.getDocId()); + let row = selectRes.length > 0 ? selectRes[0] : null; + if (utils.isUselesSfc(row, cmd)) { + ctx.logger.warn('isUselessConvertion return true. row=%j', row); + return constants.CONVERT_PARAMS; + } + } + return constants.NO_ERROR; +} +async function changeFormatToExtendedPdf(ctx, dataConvert, cmd) { + let forceSave = cmd.getForceSave(); + let isSendForm = forceSave && forceSave.getType() === commonDefines.c_oAscForceSaveTypes.Form; + let originFormat = cmd.getOriginFormat(); + let isOriginFormatWithForms = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === originFormat || + constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM === originFormat || + constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_DOCXF === originFormat; + let isFormatToPdf = constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDF === dataConvert.formatTo || + constants.AVS_OFFICESTUDIO_FILE_CROSSPLATFORM_PDFA === dataConvert.formatTo; + if (isFormatToPdf && isOriginFormatWithForms && !isSendForm) { + let format = await formatChecker.getDocumentFormatByFile(dataConvert.fileFrom); + if (constants.AVS_OFFICESTUDIO_FILE_CANVAS_WORD === format) { + ctx.logger.debug('change format to extended pdf'); + dataConvert.formatTo = constants.AVS_OFFICESTUDIO_FILE_DOCUMENT_OFORM_PDF; + } + } +} +function* replaceEmptyFile(ctx, fileFrom, ext, _lcid) { + const tenNewFileTemplate = ctx.getCfg('services.CoAuthoring.server.newFileTemplate', cfgNewFileTemplate); + if (!fs.existsSync(fileFrom) || 0 === fs.lstatSync(fileFrom).size) { + let locale = constants.TEMPLATES_DEFAULT_LOCALE; + if (_lcid) { + let localeNew = lcid.from(_lcid); + if (localeNew) { + localeNew = localeNew.replace(/_/g, '-'); + if (fs.existsSync(path.join(tenNewFileTemplate, localeNew))) { + locale = localeNew; + } else { + ctx.logger.debug('replaceEmptyFile empty locale dir locale=%s', localeNew); + } + } + } + let fileTemplatePath = path.join(tenNewFileTemplate, locale, 'new.'); + if (fs.existsSync(fileTemplatePath + ext)) { + ctx.logger.debug('replaceEmptyFile format=%s locale=%s', ext, locale); + fs.copyFileSync(fileTemplatePath + ext, fileFrom); + } else { + let format = formatChecker.getFormatFromString(ext); + let editorFormat; + if (formatChecker.isDocumentFormat(format)) { + editorFormat = 'docx'; + } else if (formatChecker.isSpreadsheetFormat(format)) { + editorFormat = 'xlsx'; + } else if (formatChecker.isPresentationFormat(format)) { + editorFormat = 'pptx'; + } + if (fs.existsSync(fileTemplatePath + editorFormat)) { + ctx.logger.debug('replaceEmptyFile format=%s locale=%s', ext, locale); + fs.copyFileSync(fileTemplatePath + editorFormat, fileFrom); + } + } + } +} +function* downloadFile(ctx, uri, fileFrom, withAuthorization, isInJwtToken, opt_headers) { + const tenMaxDownloadBytes = ctx.getCfg('FileConverter.converter.maxDownloadBytes', cfgMaxDownloadBytes); + const tenDownloadTimeout = ctx.getCfg('FileConverter.converter.downloadTimeout', cfgDownloadTimeout); + const tenDownloadAttemptMaxCount = ctx.getCfg('FileConverter.converter.downloadAttemptMaxCount', cfgDownloadAttemptMaxCount); + const tenDownloadAttemptDelay = ctx.getCfg('FileConverter.converter.downloadAttemptDelay', cfgDownloadAttemptDelay); + var res = constants.CONVERT_DOWNLOAD; var data = null; + var sha256 = null; var downloadAttemptCount = 0; var urlParsed = url.parse(uri); - var hostIp = yield utils.dnsLookup(urlParsed.hostname); - var filterStatus = utils.checkIpFilter(hostIp, urlParsed.hostname); + var filterStatus = yield* utils.checkHostFilter(ctx, urlParsed.hostname); if (0 == filterStatus) { - while (!res && downloadAttemptCount++ < cfgDownloadAttemptMaxCount) { + while (constants.NO_ERROR !== res && downloadAttemptCount++ < tenDownloadAttemptMaxCount) { try { let authorization; - if (cfgTokenEnableRequestOutbox) { - authorization = utils.fillJwtForRequest(); + if (utils.canIncludeOutboxAuthorization(ctx, uri) && withAuthorization) { + let secret = yield tenantManager.getTenantSecret(ctx, commonDefines.c_oAscSecretType.Outbox); + authorization = utils.fillJwtForRequest(ctx, {url: uri}, secret, false); } - data = yield utils.downloadUrlPromise(uri, cfgDownloadTimeout * 1000, cfgDownloadMaxBytes, authorization); - res = true; + let getRes = yield utils.downloadUrlPromise(ctx, uri, tenDownloadTimeout, tenMaxDownloadBytes, authorization, isInJwtToken, opt_headers); + data = getRes.body; + sha256 = getRes.sha256; + res = constants.NO_ERROR; } catch (err) { - res = false; - logger.error('error downloadFile:url=%s;attempt=%d;code:%s;connect:%s;(id=%s)\r\n%s', uri, downloadAttemptCount, err.code, err.connect, docId, err.stack); + res = constants.CONVERT_DOWNLOAD; + ctx.logger.error('error downloadFile:url=%s;attempt=%d;code:%s;connect:%s %s', uri, downloadAttemptCount, err.code, err.connect, err.stack); //not continue attempts if timeout - if (err.code === 'ETIMEDOUT' || err.code === 'EMSGSIZE') { + if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') { + break; + } else if (err.code === 'EMSGSIZE') { + res = constants.CONVERT_LIMITS; break; } else { - yield utils.sleep(cfgDownloadAttemptDelay); + yield utils.sleep(tenDownloadAttemptDelay); } } } - if (res) { - logger.debug('downloadFile complete(id=%s)', docId); + if (constants.NO_ERROR === res) { + ctx.logger.debug('downloadFile complete filesize=%d sha256=%s', data.length, sha256); fs.writeFileSync(fileFrom, data); } } else { - logger.error('checkIpFilter error:url=%s;code:%s;(id=%s)', uri, filterStatus, docId); - res = false; + ctx.logger.error('checkIpFilter error:url=%s;code:%s;', uri, filterStatus); + res = constants.CONVERT_DOWNLOAD; } return res; } -function promiseGetChanges(key, forceSave) { - return new Promise(function(resolve, reject) { - var time; - var index; - if (forceSave) { - time = forceSave.getTime(); - index = forceSave.getIndex(); - } - baseConnector.getChanges(key, time, index, function(err, result) { - if (err) { - reject(err); - } else { - resolve(result); - } - }); - }); -} -function* downloadFileFromStorage(id, strPath, dir) { - var list = yield storage.listObjects(strPath); - logger.debug('downloadFileFromStorage list %s (id=%s)', list.toString(), id); +function* downloadFileFromStorage(ctx, strPath, dir, opt_specialDir) { + var list = yield storage.listObjects(ctx, strPath, opt_specialDir); + ctx.logger.debug('downloadFileFromStorage list %s', list.toString()); //create dirs var dirsToCreate = []; var dirStruct = {}; @@ -263,176 +472,468 @@ function* downloadFileFromStorage(id, strPath, dir) { for (var i = 0; i < list.length; ++i) { var file = list[i]; var fileRel = storage.getRelativePath(strPath, file); - var data = yield storage.getObject(file); + var data = yield storage.getObject(ctx, file, opt_specialDir); fs.writeFileSync(path.join(dir, fileRel), data); } + return list.length; } -function* processDownloadFromStorage(dataConvert, cmd, task, tempDirs) { +function* processDownloadFromStorage(ctx, dataConvert, cmd, task, tempDirs, authorProps) { + const tenEditor = ctx.getCfg('services.CoAuthoring.editor', cfgEditor); + let res = constants.NO_ERROR; + let concatDir; + let concatTemplate; if (task.getFromOrigin() || task.getFromSettings()) { + if (task.getFromChanges()) { + let changesDir = path.join(tempDirs.source, constants.CHANGES_NAME); + fs.mkdirSync(changesDir); + let filesCount = 0; + if (cmd.getSaveKey()) { + filesCount = yield* downloadFileFromStorage(ctx, cmd.getDocId() + cmd.getSaveKey(), changesDir); + } + if (filesCount > 0) { + concatDir = changesDir; + concatTemplate = "changes0"; + } else { + dataConvert.fromChanges = false; + task.setFromChanges(dataConvert.fromChanges); + } + } dataConvert.fileFrom = path.join(tempDirs.source, 'origin.' + cmd.getFormat()); } else { - //перезапиÑываем некоторые файлы из m_sKey(например Editor.bin или changes) - yield* downloadFileFromStorage(cmd.getSaveKey(), cmd.getSaveKey(), tempDirs.source); - dataConvert.fileFrom = path.join(tempDirs.source, 'Editor.bin'); - //при необходимоÑти Ñобираем файл из чаÑтей, вида EditorN.bin - var parsedFrom = path.parse(dataConvert.fileFrom); - var list = yield utils.listObjects(parsedFrom.dir, true); - list.sort(utils.compareStringByLength); - var fsFullFile = null; - for (var i = 0; i < list.length; ++i) { - var file = list[i]; - var parsedFile = path.parse(file); - if (parsedFile.name !== parsedFrom.name && parsedFile.name.startsWith(parsedFrom.name)) { - if (!fsFullFile) { - fsFullFile = yield utils.promiseCreateWriteStream(dataConvert.fileFrom); + //overwrite some files from m_sKey (for example Editor.bin or changes) + if (cmd.getSaveKey()) { + yield* downloadFileFromStorage(ctx, cmd.getDocId() + cmd.getSaveKey(), tempDirs.source); + } + let format = cmd.getFormat() || 'bin'; + dataConvert.fileFrom = path.join(tempDirs.source, 'Editor.' + format); + concatDir = tempDirs.source; + } + if (!utils.checkPathTraversal(ctx, dataConvert.key, tempDirs.source, dataConvert.fileFrom)) { + return constants.CONVERT_PARAMS; + } + //mail merge + let mailMergeSend = cmd.getMailMergeSend(); + if (mailMergeSend) { + yield* downloadFileFromStorage(ctx, cmd.getDocId() + mailMergeSend.getJsonKey(), tempDirs.source); + concatDir = tempDirs.source; + } + if (concatDir) { + yield* concatFiles(concatDir, concatTemplate); + if (concatTemplate) { + let filenames = fs.readdirSync(concatDir); + filenames.forEach(file => { + if (file.match(new RegExp(`${concatTemplate}\\d+\\.`))) { + fs.rmSync(path.join(concatDir, file)); } - var fsCurFile = yield utils.promiseCreateReadStream(file); - yield utils.pipeStreams(fsCurFile, fsFullFile, false); + }); + } + } + //todo rework + if (!fs.existsSync(dataConvert.fileFrom)) { + if (fs.existsSync(path.join(tempDirs.source, 'origin.docx'))) { + dataConvert.fileFrom = path.join(tempDirs.source, 'origin.docx'); + } else if (fs.existsSync(path.join(tempDirs.source, 'origin.xlsx'))) { + dataConvert.fileFrom = path.join(tempDirs.source, 'origin.xlsx'); + } else if (fs.existsSync(path.join(tempDirs.source, 'origin.pptx'))) { + dataConvert.fileFrom = path.join(tempDirs.source, 'origin.pptx'); + } else if (fs.existsSync(path.join(tempDirs.source, 'origin.pdf'))) { + dataConvert.fileFrom = path.join(tempDirs.source, 'origin.pdf'); + } + if (fs.existsSync(dataConvert.fileFrom)) { + let fileFromNew = path.join(path.dirname(dataConvert.fileFrom), "Editor.bin"); + fs.renameSync(dataConvert.fileFrom, fileFromNew); + dataConvert.fileFrom = fileFromNew; + } + } + + yield changeFormatToExtendedPdf(ctx, dataConvert, cmd); + + if (task.getFromChanges() && !(task.getFromOrigin() || task.getFromSettings())) { + let sha256 = yield utils.checksumFile('sha256', dataConvert.fileFrom) + if(tenEditor['binaryChanges']) { + res = yield* processChangesBin(ctx, tempDirs, task, cmd, authorProps, sha256); + } else { + res = yield* processChangesBase64(ctx, tempDirs, task, cmd, authorProps, sha256); + } + } + return res; +} + +function* concatFiles(source, template) { + template = template || "Editor"; + //concatenate EditorN.ext parts in Editor.ext + let list = yield utils.listObjects(source, true); + list.sort(utils.compareStringByLength); + let writeStreams = {}; + for (let i = 0; i < list.length; ++i) { + let file = list[i]; + if (file.match(new RegExp(`${template}\\d+\\.`))) { + let target = file.replace(new RegExp(`(${template})\\d+(\\..*)`), '$1$2'); + let writeStream = writeStreams[target]; + if (!writeStream) { + writeStream = yield utils.promiseCreateWriteStream(target); + writeStreams[target] = writeStream; } + let readStream = yield utils.promiseCreateReadStream(file); + yield utils.pipeStreams(readStream, writeStream, false); } - if (fsFullFile) { - fsFullFile.end(); + } + for (let i in writeStreams) { + if (writeStreams.hasOwnProperty(i)) { + writeStreams[i].end(); } } - //mail merge - var mailMergeSend = cmd.getMailMergeSend(); - if (mailMergeSend) { - yield* downloadFileFromStorage(mailMergeSend.getJsonKey(), mailMergeSend.getJsonKey(), tempDirs.source); - //разбиваем на 2 файла - var data = fs.readFileSync(dataConvert.fileFrom); - var head = data.slice(0, 11).toString('ascii'); - var index = head.indexOf(';'); - if (-1 != index) { - var lengthBinary = parseInt(head.substring(0, index)); - var dataJson = data.slice(index + 1 + lengthBinary); - fs.writeFileSync(path.join(tempDirs.source, 'Editor.json'), dataJson); - var dataBinary = data.slice(index + 1, index + 1 + lengthBinary); - fs.writeFileSync(dataConvert.fileFrom, dataBinary); +} +function* processChangesBin(ctx, tempDirs, task, cmd, authorProps, sha256) { + const tenStreamWriterBufferSize = ctx.getCfg('FileConverter.converter.streamWriterBufferSize', cfgStreamWriterBufferSize); + const tenMaxRequestChanges = ctx.getCfg('services.CoAuthoring.server.maxRequestChanges', cfgMaxRequestChanges); + let res = constants.NO_ERROR; + let changesDir = path.join(tempDirs.source, constants.CHANGES_NAME); + fs.mkdirSync(changesDir); + let indexFile = 0; + let changesAuthor = null; + let changesAuthorUnique = null; + let changesIndex = null; + let changesHistory = { + serverVersion: commonDefines.buildVersion, + changes: [] + }; + let forceSave = cmd.getForceSave(); + let forceSaveTime; + let forceSaveIndex = Number.MAX_VALUE; + if (forceSave && undefined !== forceSave.getTime() && undefined !== forceSave.getIndex()) { + forceSaveTime = forceSave.getTime(); + forceSaveIndex = forceSave.getIndex(); + } + let extChangeInfo = cmd.getExternalChangeInfo(); + let extChanges; + if (extChangeInfo) { + extChanges = [{ + id: cmd.getDocId(), change_id: 0, change_data: Buffer.alloc(0), user_id: extChangeInfo.user_id, + user_id_original: extChangeInfo.user_id_original, user_name: extChangeInfo.user_name, + change_date: new Date(extChangeInfo.change_date) + }]; + } + + let streamObj = yield* streamCreateBin(ctx, changesDir, indexFile++, {highWaterMark: tenStreamWriterBufferSize}); + yield* streamWriteBin(streamObj, Buffer.from(utils.getChangesFileHeader(), 'utf-8')); + let curIndexStart = 0; + let curIndexEnd = Math.min(curIndexStart + tenMaxRequestChanges, forceSaveIndex); + while (curIndexStart < curIndexEnd || extChanges) { + let changes = []; + if (curIndexStart < curIndexEnd) { + changes = yield baseConnector.getChangesPromise(ctx, cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime); + if (changes.length > 0 && changes[0].change_data.subarray(0, 'ENCRYPTED;'.length).includes('ENCRYPTED;')) { + ctx.logger.warn('processChanges encrypted changes'); + //todo sql request instead? + res = constants.EDITOR_CHANGES; + } + res = yield* isUselessConvertion(ctx, task, cmd); + if (constants.NO_ERROR !== res) { + break; + } + } + if (0 === changes.length && extChanges) { + changes = extChanges; + } + extChanges = undefined; + for (let i = 0; i < changes.length; ++i) { + let change = changes[i]; + if (null === changesAuthor || changesAuthor !== change.user_id_original) { + if (null !== changesAuthor) { + yield* streamEndBin(streamObj); + streamObj = yield* streamCreateBin(ctx, changesDir, indexFile++); + yield* streamWriteBin(streamObj, Buffer.from(utils.getChangesFileHeader(), 'utf-8')); + } + let strDate = baseConnector.getDateTime(change.change_date); + changesHistory.changes.push({"documentSha256": sha256, 'created': strDate, 'user': {'id': change.user_id_original, 'name': change.user_name}}); + } + changesAuthor = change.user_id_original; + changesAuthorUnique = change.user_id; + yield* streamWriteBin(streamObj, change.change_data); + streamObj.isNoChangesInFile = false; + } + if (changes.length > 0) { + authorProps.lastModifiedBy = changes[changes.length - 1].user_name; + authorProps.modified = changes[changes.length - 1].change_date.toISOString().slice(0, 19) + 'Z'; + } + if (changes.length === curIndexEnd - curIndexStart) { + curIndexStart += tenMaxRequestChanges; + curIndexEnd = Math.min(curIndexStart + tenMaxRequestChanges, forceSaveIndex); } else { - logger.error('mail merge format (id=%s)', cmd.getDocId()); - } - } - if (task.getFromChanges()) { - var changesDir = path.join(tempDirs.source, 'changes'); - fs.mkdirSync(changesDir); - var indexFile = 0; - var changesAuthor = null; - var changesHistory = { - serverVersion: commonDefines.buildVersion, - changes: [] - }; - //todo writeable stream - let changesBuffers = null; - let changes = yield promiseGetChanges(cmd.getDocId(), cmd.getForceSave()); - for (var i = 0; i < changes.length; ++i) { - var change = changes[i]; + break; + } + } + yield* streamEndBin(streamObj); + if (streamObj.isNoChangesInFile) { + fs.unlinkSync(streamObj.filePath); + } + if (null !== changesAuthorUnique) { + changesIndex = utils.getIndexFromUserId(changesAuthorUnique, changesAuthor); + } + if (null == changesAuthor && null == changesIndex && forceSave && undefined !== forceSave.getAuthorUserId() && + undefined !== forceSave.getAuthorUserIndex()) { + changesAuthor = forceSave.getAuthorUserId(); + changesIndex = forceSave.getAuthorUserIndex(); + } + cmd.setUserId(changesAuthor); + cmd.setUserIndex(changesIndex); + fs.writeFileSync(path.join(tempDirs.result, 'changesHistory.json'), JSON.stringify(changesHistory), 'utf8'); + ctx.logger.debug('processChanges end'); + return res; +} + +function* streamCreateBin(ctx, changesDir, indexFile, opt_options) { + let fileName = constants.CHANGES_NAME + indexFile + '.bin'; + let filePath = path.join(changesDir, fileName); + let writeStream = yield utils.promiseCreateWriteStream(filePath, opt_options); + writeStream.on('error', function(err) { + //todo integrate error handle in main thread (probable: set flag here and check it in main thread) + ctx.logger.error('WriteStreamError %s', err.stack); + }); + return {writeStream: writeStream, filePath: filePath, isNoChangesInFile: true}; +} + +function* streamWriteBin(streamObj, buf) { + if (!streamObj.writeStream.write(buf)) { + yield utils.promiseWaitDrain(streamObj.writeStream); + } +} + +function* streamEndBin(streamObj) { + streamObj.writeStream.end(); + yield utils.promiseWaitClose(streamObj.writeStream); +} +function* processChangesBase64(ctx, tempDirs, task, cmd, authorProps, sha256) { + const tenStreamWriterBufferSize = ctx.getCfg('FileConverter.converter.streamWriterBufferSize', cfgStreamWriterBufferSize); + const tenMaxRequestChanges = ctx.getCfg('services.CoAuthoring.server.maxRequestChanges', cfgMaxRequestChanges); + let res = constants.NO_ERROR; + let changesDir = path.join(tempDirs.source, constants.CHANGES_NAME); + fs.mkdirSync(changesDir); + let indexFile = 0; + let changesAuthor = null; + let changesAuthorUnique = null; + let changesIndex = null; + let changesHistory = { + serverVersion: commonDefines.buildVersion, + changes: [] + }; + let forceSave = cmd.getForceSave(); + let forceSaveTime; + let forceSaveIndex = Number.MAX_VALUE; + if (forceSave && undefined !== forceSave.getTime() && undefined !== forceSave.getIndex()) { + forceSaveTime = forceSave.getTime(); + forceSaveIndex = forceSave.getIndex(); + } + let extChangeInfo = cmd.getExternalChangeInfo(); + let extChanges; + if (extChangeInfo) { + extChanges = [{ + id: cmd.getDocId(), change_id: 0, change_data: "", user_id: extChangeInfo.user_id, + user_id_original: extChangeInfo.user_id_original, user_name: extChangeInfo.user_name, + change_date: new Date(extChangeInfo.change_date) + }]; + } + + let streamObj = yield* streamCreate(ctx, changesDir, indexFile++, {highWaterMark: tenStreamWriterBufferSize}); + let curIndexStart = 0; + let curIndexEnd = Math.min(curIndexStart + tenMaxRequestChanges, forceSaveIndex); + while (curIndexStart < curIndexEnd || extChanges) { + let changes = []; + if (curIndexStart < curIndexEnd) { + changes = yield baseConnector.getChangesPromise(ctx, cmd.getDocId(), curIndexStart, curIndexEnd, forceSaveTime); + if (changes.length > 0 && changes[0].change_data.startsWith('ENCRYPTED;')) { + ctx.logger.warn('processChanges encrypted changes'); + //todo sql request instead? + res = constants.EDITOR_CHANGES; + } + res = yield* isUselessConvertion(ctx, task, cmd); + if (constants.NO_ERROR !== res) { + break; + } + } + if (0 === changes.length && extChanges) { + changes = extChanges; + } + extChanges = undefined; + for (let i = 0; i < changes.length; ++i) { + let change = changes[i]; if (null === changesAuthor || changesAuthor !== change.user_id_original) { if (null !== changesAuthor) { - changesBuffers.push(new Buffer(']', 'utf8')); - let dataZipFile = Buffer.concat(changesBuffers); - changesBuffers = null; - var fileName = 'changes' + (indexFile++) + '.json'; - var filePath = path.join(changesDir, fileName); - fs.writeFileSync(filePath, dataZipFile); + yield* streamEnd(streamObj, ']'); + streamObj = yield* streamCreate(ctx, changesDir, indexFile++); } - changesAuthor = change.user_id_original; - var strDate = baseConnector.getDateTime(change.change_date); - changesHistory.changes.push({ - 'created': strDate, 'user': { - 'id': changesAuthor, 'name': change.user_name - } - }); - changesBuffers = []; - changesBuffers.push(new Buffer('[', 'utf8')); + let strDate = baseConnector.getDateTime(change.change_date); + changesHistory.changes.push({"documentSha256": sha256, 'created': strDate, 'user': {'id': change.user_id_original, 'name': change.user_name}}); + yield* streamWrite(streamObj, '['); } else { - changesBuffers.push(new Buffer(',', 'utf8')); + yield* streamWrite(streamObj, ','); } - changesBuffers.push(new Buffer(change.change_data, 'utf8')); + changesAuthor = change.user_id_original; + changesAuthorUnique = change.user_id; + yield* streamWrite(streamObj, change.change_data); + streamObj.isNoChangesInFile = false; + } + if (changes.length > 0) { + authorProps.lastModifiedBy = changes[changes.length - 1].user_name; + authorProps.modified = changes[changes.length - 1].change_date.toISOString().slice(0, 19) + 'Z'; } - if (null !== changesBuffers) { - changesBuffers.push(new Buffer(']', 'utf8')); - let dataZipFile = Buffer.concat(changesBuffers); - changesBuffers = null; - var fileName = 'changes' + (indexFile++) + '.json'; - var filePath = path.join(changesDir, fileName); - fs.writeFileSync(filePath, dataZipFile); + if (changes.length === curIndexEnd - curIndexStart) { + curIndexStart += tenMaxRequestChanges; + curIndexEnd = Math.min(curIndexStart + tenMaxRequestChanges, forceSaveIndex); + } else { + break; } - cmd.setUserId(changesAuthor); - fs.writeFileSync(path.join(tempDirs.result, 'changesHistory.json'), JSON.stringify(changesHistory), 'utf8'); } + yield* streamEnd(streamObj, ']'); + if (streamObj.isNoChangesInFile) { + fs.unlinkSync(streamObj.filePath); + } + if (null !== changesAuthorUnique) { + changesIndex = utils.getIndexFromUserId(changesAuthorUnique, changesAuthor); + } + if (null == changesAuthor && null == changesIndex && forceSave && undefined !== forceSave.getAuthorUserId() && + undefined !== forceSave.getAuthorUserIndex()) { + changesAuthor = forceSave.getAuthorUserId(); + changesIndex = forceSave.getAuthorUserIndex(); + } + cmd.setUserId(changesAuthor); + cmd.setUserIndex(changesIndex); + fs.writeFileSync(path.join(tempDirs.result, 'changesHistory.json'), JSON.stringify(changesHistory), 'utf8'); + ctx.logger.debug('processChanges end'); + return res; +} + +function* streamCreate(ctx, changesDir, indexFile, opt_options) { + let fileName = constants.CHANGES_NAME + indexFile + '.json'; + let filePath = path.join(changesDir, fileName); + let writeStream = yield utils.promiseCreateWriteStream(filePath, opt_options); + writeStream.on('error', function(err) { + //todo integrate error handle in main thread (probable: set flag here and check it in main thread) + ctx.logger.error('WriteStreamError %s', err.stack); + }); + return {writeStream: writeStream, filePath: filePath, isNoChangesInFile: true}; +} + +function* streamWrite(streamObj, text) { + if (!streamObj.writeStream.write(text, 'utf8')) { + yield utils.promiseWaitDrain(streamObj.writeStream); + } +} + +function* streamEnd(streamObj, text) { + streamObj.writeStream.end(text, 'utf8'); + yield utils.promiseWaitClose(streamObj.writeStream); } -function* processUploadToStorage(dir, storagePath) { +function* processUploadToStorage(ctx, dir, storagePath, calcChecksum, opt_specialDirDst, opt_ignorPrefix) { var list = yield utils.listObjects(dir); + if (opt_ignorPrefix) { + list = list.filter((dir) => !dir.startsWith(opt_ignorPrefix)); + } if (list.length < MAX_OPEN_FILES) { - yield* processUploadToStorageChunk(list, dir, storagePath); + yield* processUploadToStorageChunk(ctx, list, dir, storagePath, calcChecksum, opt_specialDirDst); } else { for (var i = 0, j = list.length; i < j; i += MAX_OPEN_FILES) { - yield* processUploadToStorageChunk(list.slice(i, i + MAX_OPEN_FILES), dir, storagePath); + yield* processUploadToStorageChunk(ctx, list.slice(i, i + MAX_OPEN_FILES), dir, storagePath, calcChecksum, opt_specialDirDst); } } } -function* processUploadToStorageChunk(list, dir, storagePath) { - yield Promise.all(list.map(function (curValue) { - var data = fs.readFileSync(curValue); - var localValue = storagePath + '/' + curValue.substring(dir.length + 1); - return storage.putObject(localValue, data, data.length); - })); +function* processUploadToStorageChunk(ctx, list, dir, storagePath, calcChecksum, opt_specialDirDst) { + let promises = list.reduce(function(r, curValue) { + let localValue = storagePath + '/' + curValue.substring(dir.length + 1); + let checksum; + if (calcChecksum) { + checksum = utils.checksumFile('sha256', curValue).then(result => { + ctx.logger.debug('processUploadToStorageChunk path=%s; sha256=%s', localValue, result); + }); + } + let upload = storage.uploadObject(ctx, localValue, curValue, opt_specialDirDst); + r.push(checksum, upload); + return r; + }, []); + yield Promise.all(promises); +} +function* processUploadToStorageErrorFile(ctx, dataConvert, tempDirs, childRes, exitCode, exitSignal, error) { + const tenErrorFiles = ctx.getCfg('FileConverter.converter.errorfiles', cfgErrorFiles); + if (!tenErrorFiles) { + return; + } + let output = ''; + if (undefined !== childRes.stdout) { + output += `stdout:${childRes.stdout}\n`; + } + if (undefined !== childRes.stderr) { + output += `stderr:${childRes.stderr}\n`; + } + output += `ExitCode (code=${exitCode};signal=${exitSignal};error:${error})`; + let outputPath = path.join(tempDirs.temp, 'console.txt'); + fs.writeFileSync(outputPath, output, {encoding: 'utf8'}); + + //ignore result dir with temp dir inside(see m_sTempDir param) to reduce the amount of data transferred + let ignorePrefix = path.normalize(tempDirs.result); + let format = path.extname(dataConvert.fileFrom).substring(1) || "unknown"; + + yield* processUploadToStorage(ctx, tempDirs.temp, format + '/' + dataConvert.key , false, tenErrorFiles, ignorePrefix); + ctx.logger.debug('processUploadToStorage error complete(id=%s)', dataConvert.key); } -function writeProcessOutputToLog(docId, childRes, isDebug) { +function writeProcessOutputToLog(ctx, childRes, isDebug) { if (childRes) { - if (childRes.stdout) { + if (undefined !== childRes.stdout) { if (isDebug) { - logger.debug('stdout (id=%s):%s', docId, childRes.stdout); + ctx.logger.debug('stdout:%s', childRes.stdout); } else { - logger.error('stdout (id=%s):%s', docId, childRes.stdout); + ctx.logger.error('stdout:%s', childRes.stdout); } } - if (childRes.stderr) { + if (undefined !== childRes.stderr) { if (isDebug) { - logger.debug('stderr (id=%s):%s', docId, childRes.stderr); + ctx.logger.debug('stderr:%s', childRes.stderr); } else { - logger.error('stderr (id=%s):%s', docId, childRes.stderr); + ctx.logger.error('stderr:%s', childRes.stderr); } } } } -function* postProcess(cmd, dataConvert, tempDirs, childRes, error) { +function* postProcess(ctx, cmd, dataConvert, tempDirs, childRes, error, isTimeout) { var exitCode = 0; var exitSignal = null; - var errorCode = null; if(childRes) { exitCode = childRes.status; exitSignal = childRes.signal; - if (childRes.error) { - errorCode = childRes.error.code; - } } - if (0 !== exitCode || null !== exitSignal) { + //CONVERT_CELLLIMITS is not an error, but an indicator that data was lost during opening (can be displayed as an error) + if ((0 !== exitCode && constants.CONVERT_CELLLIMITS !== -exitCode) || null !== exitSignal) { if (-1 !== exitCodesReturn.indexOf(-exitCode)) { error = -exitCode; - } else if('ETIMEDOUT' === errorCode) { + } else if(isTimeout) { error = constants.CONVERT_TIMEOUT; } else { error = constants.CONVERT; } if (-1 !== exitCodesMinorError.indexOf(error)) { - writeProcessOutputToLog(dataConvert.key, childRes, true); - logger.debug('ExitCode (code=%d;signal=%s;error:%d;id=%s)', exitCode, exitSignal, error, dataConvert.key); + writeProcessOutputToLog(ctx, childRes, true); + ctx.logger.debug('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error); } else { - writeProcessOutputToLog(dataConvert.key, childRes, false); - logger.error('ExitCode (code=%d;signal=%s;error:%d;id=%s)', exitCode, exitSignal, error, dataConvert.key); - if (cfgErrorFiles) { - yield* processUploadToStorage(tempDirs.temp, cfgErrorFiles + '/' + dataConvert.key); - logger.debug('processUploadToStorage error complete(id=%s)', dataConvert.key); - } + writeProcessOutputToLog(ctx, childRes, false); + ctx.logger.error('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error); + yield* processUploadToStorageErrorFile(ctx, dataConvert, tempDirs, childRes, exitCode, exitSignal, error); } } else { - writeProcessOutputToLog(dataConvert.key, childRes, true); - logger.debug('ExitCode (code=%d;signal=%s;error:%d;id=%s)', exitCode, exitSignal, error, dataConvert.key); + writeProcessOutputToLog(ctx, childRes, true); + ctx.logger.debug('ExitCode (code=%d;signal=%s;error:%d)', exitCode, exitSignal, error); } if (-1 !== exitCodesUpload.indexOf(error)) { - yield* processUploadToStorage(tempDirs.result, dataConvert.key); - logger.debug('processUploadToStorage complete(id=%s)', dataConvert.key); + if (-1 !== exitCodesCopyOrigin.indexOf(error)) { + let originPath = path.join(path.dirname(dataConvert.fileTo), "origin" + path.extname(dataConvert.fileFrom)); + if (!fs.existsSync(dataConvert.fileTo)) { + fs.copyFileSync(dataConvert.fileFrom, originPath); + ctx.logger.debug('copyOrigin complete'); + } + } + //todo clarify calcChecksum conditions + let calcChecksum = (0 === (constants.AVS_OFFICESTUDIO_FILE_CANVAS & cmd.getOutputFormat())); + yield* processUploadToStorage(ctx, tempDirs.result, dataConvert.key, calcChecksum); + ctx.logger.debug('processUploadToStorage complete'); } cmd.setStatusInfo(error); var existFile = false; @@ -442,8 +943,8 @@ function* postProcess(cmd, dataConvert, tempDirs, childRes, error) { existFile = false; } if (!existFile) { - //todo переÑмотреть. загрулка в Ñлучае AVS_OFFICESTUDIO_FILE_OTHER_TEAMLAB_INNER x2t менÑет раÑширение у файла. - var fileToBasename = path.basename(dataConvert.fileTo); + //todo review. the stub in the case of AVS_OFFICESTUDIO_FILE_OTHER_OOXML x2t changes the file extension. + var fileToBasename = path.basename(dataConvert.fileTo, path.extname(dataConvert.fileTo)); var fileToDir = path.dirname(dataConvert.fileTo); var files = fs.readdirSync(fileToDir); for (var i = 0; i < files.length; ++i) { @@ -459,27 +960,85 @@ function* postProcess(cmd, dataConvert, tempDirs, childRes, error) { cmd.setTitle(cmd.getOutputPath()); } - var res = new commonDefines.TaskQueueData(); - res.setCmd(cmd); - logger.debug('output (data=%s;id=%s)', JSON.stringify(res), dataConvert.key); - return res; + var queueData = new commonDefines.TaskQueueData(); + queueData.setCtx(ctx); + queueData.setCmd(cmd); + ctx.logger.debug('output (data=%j)', queueData); + return queueData; } -function deleteFolderRecursive(strPath) { - if (fs.existsSync(strPath)) { - var files = fs.readdirSync(strPath); - files.forEach(function(file) { - var curPath = path.join(strPath, file); - if (fs.lstatSync(curPath).isDirectory()) { // recurse - deleteFolderRecursive(curPath); - } else { // delete file - fs.unlinkSync(curPath); - } - }); - fs.rmdirSync(strPath); + +function* spawnProcess(ctx, builderParams, tempDirs, dataConvert, authorProps, getTaskTime, task, isInJwtToken) { + const tenX2tPath = ctx.getCfg('FileConverter.converter.x2tPath', cfgX2tPath); + const tenDocbuilderPath = ctx.getCfg('FileConverter.converter.docbuilderPath', cfgDocbuilderPath); + const tenArgs = ctx.getCfg('FileConverter.converter.args', cfgArgs); + let childRes, isTimeout = false; + let childArgs; + if (tenArgs.length > 0) { + childArgs = tenArgs.trim().replace(/ +/g, ' ').split(' '); + } else { + childArgs = []; + } + let processPath; + if (!builderParams) { + processPath = tenX2tPath; + let paramsFile = path.join(tempDirs.temp, 'params.xml'); + dataConvert.serialize(ctx, paramsFile); + childArgs.push(paramsFile); + let hiddenXml = yield dataConvert.serializeHidden(ctx); + if (hiddenXml) { + childArgs.push(hiddenXml); + } + } else { + fs.mkdirSync(path.join(tempDirs.result, 'output')); + processPath = tenDocbuilderPath; + childArgs.push('--check-fonts=0'); + childArgs.push('--save-use-only-names=' + tempDirs.result + '/output'); + if (builderParams.argument) { + childArgs.push(`--argument=${JSON.stringify(builderParams.argument)}`); + } + childArgs.push('--options=' + dataConvert.serializeOptions(ctx, isInJwtToken)); + childArgs.push(dataConvert.fileFrom); + } + let timeoutId; + try { + const tenSpawnOptions = ctx.getCfg('FileConverter.converter.spawnOptions', cfgSpawnOptions); + //copy to avoid modification of global cfgSpawnOptions + let spawnOptions = Object.assign({}, tenSpawnOptions);; + spawnOptions.env = Object.assign({}, process.env, spawnOptions.env); + if (authorProps.lastModifiedBy && authorProps.modified) { + spawnOptions.env['LAST_MODIFIED_BY'] = authorProps.lastModifiedBy; + spawnOptions.env['MODIFIED'] = authorProps.modified; + } + let spawnAsyncPromise = spawnAsync(processPath, childArgs, spawnOptions); + childRes = spawnAsyncPromise.child; + let waitMS = Math.max(0, task.getVisibilityTimeout() * 1000 - (new Date().getTime() - getTaskTime.getTime())); + timeoutId = setTimeout(function() { + isTimeout = true; + timeoutId = undefined; + //close stdio streams to enable emit 'close' event even if HtmlFileInternal is hung-up + childRes.stdin.end(); + childRes.stdout.destroy(); + childRes.stderr.destroy(); + childRes.kill(); + }, waitMS); + childRes = yield spawnAsyncPromise; + } catch (err) { + if (null === err.status) { + ctx.logger.error('error spawnAsync %s', err.stack); + } else { + ctx.logger.debug('error spawnAsync %s', err.stack); + } + childRes = err; } + if (undefined !== timeoutId) { + clearTimeout(timeoutId); + } + return {childRes: childRes, isTimeout: isTimeout}; } -function* ExecuteTask(task) { +function* ExecuteTask(ctx, task) { + const tenForgottenFiles = ctx.getCfg('services.CoAuthoring.server.forgottenfiles', cfgForgottenFiles); + const tenForgottenFilesName = ctx.getCfg('services.CoAuthoring.server.forgottenfilesname', cfgForgottenFilesName); var startDate = null; var curDate = null; if(clientStatsD) { @@ -489,64 +1048,108 @@ function* ExecuteTask(task) { var tempDirs; var getTaskTime = new Date(); var cmd = task.getCmd(); - var dataConvert = new TaskQueueDataConvert(task); - logger.debug('Start Task(id=%s)', dataConvert.key); + var dataConvert = new TaskQueueDataConvert(ctx, task); + ctx.logger.info('Start Task'); var error = constants.NO_ERROR; tempDirs = getTempDir(); - dataConvert.fileTo = path.join(tempDirs.result, task.getToFile()); - if (cmd.getUrl()) { - dataConvert.fileFrom = path.join(tempDirs.source, dataConvert.key + '.' + cmd.getFormat()); - var isDownload = yield* downloadFile(dataConvert.key, cmd.getUrl(), dataConvert.fileFrom); - if (!isDownload) { - error = constants.CONVERT_DOWNLOAD; - } - if(clientStatsD) { - clientStatsD.timing('conv.downloadFile', new Date() - curDate); - curDate = new Date(); + let fileTo = task.getToFile(); + dataConvert.fileTo = fileTo ? path.join(tempDirs.result, fileTo) : ''; + let builderParams = cmd.getBuilderParams(); + let authorProps = {lastModifiedBy: null, modified: null}; + let isInJwtToken = cmd.getWithAuthorization(); + error = yield* isUselessConvertion(ctx, task, cmd); + if (constants.NO_ERROR !== error) { + ; + } else if (cmd.getUrl()) { + let format = cmd.getFormat(); + dataConvert.fileFrom = path.join(tempDirs.source, dataConvert.key + '.' + format); + if (utils.checkPathTraversal(ctx, dataConvert.key, tempDirs.source, dataConvert.fileFrom)) { + let url = cmd.getUrl(); + let withAuthorization = cmd.getWithAuthorization(); + let headers; + let fileSize; + let wopiParams = cmd.getWopiParams(); + if (wopiParams) { + withAuthorization = false; + isInJwtToken = true; + let fileInfo = wopiParams.commonInfo?.fileInfo; + fileSize = fileInfo?.Size; + ({url, headers} = yield wopiClient.getWopiFileUrl(ctx, fileInfo, wopiParams.userAuth)); + } + if (undefined === fileSize || fileSize > 0) { + error = yield* downloadFile(ctx, url, dataConvert.fileFrom, withAuthorization, isInJwtToken, headers); + } + if (constants.NO_ERROR === error) { + yield* replaceEmptyFile(ctx, dataConvert.fileFrom, format, cmd.getLCID()); + } + if(clientStatsD) { + clientStatsD.timing('conv.downloadFile', new Date() - curDate); + curDate = new Date(); + } + } else { + error = constants.CONVERT_PARAMS; } - } else if (cmd.getSaveKey()) { - yield* downloadFileFromStorage(cmd.getDocId(), cmd.getDocId(), tempDirs.source); - logger.debug('downloadFileFromStorage complete(id=%s)', dataConvert.key); + } else if (cmd.getSaveKey() || task.getFromOrigin() || task.getFromSettings()) { + yield* downloadFileFromStorage(ctx, cmd.getDocId(), tempDirs.source); + ctx.logger.debug('downloadFileFromStorage complete'); if(clientStatsD) { clientStatsD.timing('conv.downloadFileFromStorage', new Date() - curDate); curDate = new Date(); } - yield* processDownloadFromStorage(dataConvert, cmd, task, tempDirs); + error = yield* processDownloadFromStorage(ctx, dataConvert, cmd, task, tempDirs, authorProps); + } else if (cmd.getForgotten()) { + yield* downloadFileFromStorage(ctx, cmd.getForgotten(), tempDirs.source, tenForgottenFiles); + ctx.logger.debug('downloadFileFromStorage complete'); + let list = yield utils.listObjects(tempDirs.source, false); + if (list.length > 0) { + dataConvert.fileFrom = list[0]; + //store indicator file to determine if opening was from the forgotten file + var forgottenMarkPath = tempDirs.result + '/' + tenForgottenFilesName + '.txt'; + fs.writeFileSync(forgottenMarkPath, tenForgottenFilesName, {encoding: 'utf8'}); + } else { + error = constants.UNKNOWN; + } + } else if (builderParams) { + //in cause script in POST body + yield* downloadFileFromStorage(ctx, cmd.getDocId(), tempDirs.source); + ctx.logger.debug('downloadFileFromStorage complete'); + let list = yield utils.listObjects(tempDirs.source, false); + if (list.length > 0) { + dataConvert.fileFrom = list[0]; + } } else { error = constants.UNKNOWN; } - var childRes = null; + let childRes = null; + let isTimeout = false; if (constants.NO_ERROR === error) { - if(constants.AVS_OFFICESTUDIO_FILE_OTHER_HTMLZIP === dataConvert.formatTo && cmd.getSaveKey() && !dataConvert.mailMergeSend) { - //todo заглушка.вÑÑ ÐºÐ¾Ð½Ð²ÐµÑ€Ñ‚Ð°Ñ†Ð¸Ñ Ð½Ð° клиенте, но нет проÑтого механизма ÑÐ¾Ñ…Ñ€Ð°Ð½ÐµÐ½Ð¸Ñ Ð½Ð° клиенте - yield utils.pipeFiles(dataConvert.fileFrom, dataConvert.fileTo); - } else { - var paramsFile = path.join(tempDirs.temp, 'params.xml'); - dataConvert.serialize(paramsFile); - var childArgs; - if (cfgArgs.length > 0) { - childArgs = cfgArgs.trim().replace(/ +/g, ' ').split(' '); - } else { - childArgs = []; - } - childArgs.push(paramsFile); - var waitMS = task.getVisibilityTimeout() * 1000 - (new Date().getTime() - getTaskTime.getTime()); - childRes = childProcess.spawnSync(cfgFilePath, childArgs, {timeout: waitMS}); + ({childRes, isTimeout} = yield* spawnProcess(ctx, builderParams, tempDirs, dataConvert, authorProps, getTaskTime, task, isInJwtToken)); + const canRollback = childRes && 0 !== childRes.status && !isTimeout && task.getFromChanges() + && constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML !== dataConvert.formatTo + && !formatChecker.isOOXFormat(dataConvert.formatTo) && !formatChecker.isBrowserEditorFormat(dataConvert.formatTo) + && !cmd.getWopiParams(); + if (canRollback) { + ctx.logger.warn('rollback to save changes to ooxml. See assemblyFormatAsOrigin param. formatTo=%s', formatChecker.getStringFromFormat(dataConvert.formatTo)); + let extOld = path.extname(dataConvert.fileTo); + let extNew = '.' + formatChecker.getStringFromFormat(constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML); + dataConvert.formatTo = constants.AVS_OFFICESTUDIO_FILE_OTHER_OOXML; + dataConvert.fileTo = dataConvert.fileTo.slice(0, -extOld.length) + extNew; + ({childRes, isTimeout} = yield* spawnProcess(ctx, builderParams, tempDirs, dataConvert, authorProps, getTaskTime, task, isInJwtToken)); } if(clientStatsD) { clientStatsD.timing('conv.spawnSync', new Date() - curDate); curDate = new Date(); } } - resData = yield* postProcess(cmd, dataConvert, tempDirs, childRes, error); - logger.debug('postProcess (id=%s)', dataConvert.key); + resData = yield* postProcess(ctx, cmd, dataConvert, tempDirs, childRes, error, isTimeout); + ctx.logger.debug('postProcess'); if(clientStatsD) { clientStatsD.timing('conv.postProcess', new Date() - curDate); curDate = new Date(); } if (tempDirs) { - deleteFolderRecursive(tempDirs.temp); - logger.debug('deleteFolderRecursive (id=%s)', dataConvert.key); + fs.rmSync(tempDirs.temp, { recursive: true, force: true }); + ctx.logger.debug('deleteFolderRecursive'); if(clientStatsD) { clientStatsD.timing('conv.deleteFolderRecursive', new Date() - curDate); curDate = new Date(); @@ -555,45 +1158,92 @@ function* ExecuteTask(task) { if(clientStatsD) { clientStatsD.timing('conv.allconvert', new Date() - startDate); } + ctx.logger.info('End Task'); return resData; } - -function receiveTask(data, dataRaw) { +function ackTask(ctx, res, task, ack) { + return co(function*() { + try { + if (!res) { + res = createErrorResponse(ctx, task); + } + if (res) { + yield queue.addResponse(res); + ctx.logger.info('ackTask addResponse'); + } + } catch (err) { + ctx.logger.error('ackTask %s', err.stack); + } finally { + ack(); + ctx.logger.info('ackTask ack'); + } + }); +} +function receiveTaskSetTimeout(ctx, task, ack, outParams) { + //add DownloadTimeout to upload results + let delay = task.getVisibilityTimeout() * 1000 + ms(cfgDownloadTimeout.wholeCycle); + return setTimeout(function() { + return co(function*() { + outParams.isAck = true; + ctx.logger.error('receiveTask timeout %d', delay); + yield ackTask(ctx, null, task, ack); + yield queue.closeOrWait(); + process.exit(1); + }); + }, delay); +} +function receiveTask(data, ack) { return co(function* () { var res = null; var task = null; + let outParams = {isAck: false}; + let timeoutId = undefined; + let ctx = new operationContext.Context(); try { task = new commonDefines.TaskQueueData(JSON.parse(data)); if (task) { - res = yield* ExecuteTask(task); + ctx.initFromTaskQueueData(task); + yield ctx.initTenantCache(); + timeoutId = receiveTaskSetTimeout(ctx, task, ack, outParams); + res = yield* ExecuteTask(ctx, task); } } catch (err) { - logger.error(err); + ctx.logger.error('receiveTask %s', err.stack); } finally { - try { - if (!res && task) { - //еÑли вÑе упало так что даже нет res, вÑе равно пытаемÑÑ Ð¾Ñ‚Ð´Ð°Ñ‚ÑŒ ошибку. - var cmd = task.getCmd(); - cmd.setStatusInfo(constants.CONVERT); - res = new commonDefines.TaskQueueData(); - res.setCmd(cmd); - } - if(res) { - yield queue.addResponse(res); - } - yield queue.removeTask(dataRaw); - } catch (err) { - logger.error(err); + clearTimeout(timeoutId); + if (!outParams.isAck) { + yield ackTask(ctx, res, task, ack); } } }); } +function createErrorResponse(ctx, task){ + if (!task) { + return null; + } + ctx.logger.debug('createErrorResponse'); + //simulate error response + let cmd = task.getCmd(); + cmd.setStatusInfo(constants.CONVERT_TEMPORARY); + let res = new commonDefines.TaskQueueData(); + res.setCtx(ctx); + res.setCmd(cmd); + return res; +} +function simulateErrorResponse(data){ + let task = new commonDefines.TaskQueueData(JSON.parse(data)); + let ctx = new operationContext.Context(); + ctx.initFromTaskQueueData(task); + //todo + //yield ctx.initTenantCache(); + return createErrorResponse(ctx, task); +} function run() { - queue = new queueService(); + queue = new queueService(simulateErrorResponse); queue.on('task', receiveTask); - queue.init(false, true, true, false, function(err) { + queue.init(true, true, true, false, false, false, function(err) { if (null != err) { - logger.error('createTaskQueue error :\r\n%s', err.stack); + operationContext.global.logger.error('createTaskQueue error: %s', err.stack); } }); } diff --git a/FileConverter/sources/convertermaster.js b/FileConverter/sources/convertermaster.js index f855cbbd6e..2209e8c956 100644 --- a/FileConverter/sources/convertermaster.js +++ b/FileConverter/sources/convertermaster.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -34,20 +34,27 @@ const cluster = require('cluster'); const logger = require('./../../Common/sources/logger'); +const operationContext = require('./../../Common/sources/operationContext'); if (cluster.isMaster) { const fs = require('fs'); const co = require('co'); - const numCPUs = require('os').cpus().length; - const configCommon = require('config'); - const config = configCommon.get('FileConverter.converter'); + const os = require('os'); + const config = require('config'); const license = require('./../../Common/sources/license'); - const cfgMaxProcessCount = config.get('maxprocesscount'); - var licenseInfo, workersCount = 0; - const readLicense = function* () { - licenseInfo = yield* license.readLicense(); - workersCount = Math.min(licenseInfo.count, Math.ceil(numCPUs * cfgMaxProcessCount)); + const cfgLicenseFile = config.get('license.license_file'); + const cfgMaxProcessCount = config.get('FileConverter.converter.maxprocesscount'); + + var workersCount = 0; + const readLicense = async function () { + const numCPUs = os.cpus().length; + const availableParallelism = os.availableParallelism?.(); + operationContext.global.logger.warn('num of CPUs: %d; availableParallelism: %s', numCPUs, availableParallelism); + workersCount = Math.ceil((availableParallelism || numCPUs) * cfgMaxProcessCount); + let [licenseInfo] = await license.readLicense(cfgLicenseFile); + workersCount = Math.min(licenseInfo.count, workersCount); + //todo send license to workers for multi-tenancy }; const updateWorkers = () => { var i; @@ -55,7 +62,7 @@ if (cluster.isMaster) { if (arrKeyWorkers.length < workersCount) { for (i = arrKeyWorkers.length; i < workersCount; ++i) { const newWorker = cluster.fork(); - logger.warn('worker %s started.', newWorker.process.pid); + operationContext.global.logger.warn('worker %s started.', newWorker.process.pid); } } else { for (i = workersCount; i < arrKeyWorkers.length; ++i) { @@ -66,26 +73,24 @@ if (cluster.isMaster) { } } }; - const updateLicense = () => { - return co(function*() { - try { - yield* readLicense(); - logger.warn('update cluster with %s workers', workersCount); - updateWorkers(); - } catch (err) { - logger.error('updateLicense error:\r\n%s', err.stack); - } - }); + const updateLicense = async () => { + try { + await readLicense(); + operationContext.global.logger.warn('update cluster with %s workers', workersCount); + updateWorkers(); + } catch (err) { + operationContext.global.logger.error('updateLicense error: %s', err.stack); + } }; cluster.on('exit', (worker, code, signal) => { - logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal); + operationContext.global.logger.warn('worker %s died (code = %s; signal = %s).', worker.process.pid, code, signal); updateWorkers(); }); updateLicense(); - fs.watchFile(configCommon.get('license').get('license_file'), updateLicense); + fs.watchFile(cfgLicenseFile, updateLicense); setInterval(updateLicense, 86400000); } else { const converter = require('./converter'); @@ -93,8 +98,8 @@ if (cluster.isMaster) { } process.on('uncaughtException', (err) => { - logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); - logger.error(err.stack); + operationContext.global.logger.error((new Date).toUTCString() + ' uncaughtException:', err.message); + operationContext.global.logger.error(err.stack); logger.shutdown(() => { process.exit(1); }); diff --git a/Gruntfile.js b/Gruntfile.js index 56418e82db..f42a0795bb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -29,10 +29,52 @@ * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode * */ - +const path = require('path'); +const _ = require('lodash'); var packageFile = require('./package.json'); module.exports = function (grunt) { + + let addons = grunt.option('addon') || []; + if (!Array.isArray(addons)) + addons = [addons]; + + addons.forEach((element,index,self) => self[index] = path.join('..', element)); + addons = addons.filter(element => grunt.file.isDir(element)); + + function _merge(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + for (const key in source) { + if (_.isObject(source[key])) { + if (_.isArray(source[key])) { + if (!_.isArray(target[key])){ + target[key]=[]; + } + target[key].push(...source[key]) + } + else { + if (!target[key]) { + Object.assign(target, { [key]: {} }); + } + _merge(target[key], source[key]); + } + } + else { + Object.assign(target, { [key]: source[key] }); + } + } + } + addons.forEach(element => { + let _path = path.join(element, 'package.json'); + if (grunt.file.exists(_path)) { + _merge(packageFile, require(_path)); + grunt.log.ok('addon '.green + element + ' is merged successfully'.green); + } + }); + + //grunt.file.write("package-test.json", JSON.stringify(packageFile, null, 4)); var checkDependencies = {}; @@ -47,28 +89,9 @@ module.exports = function (grunt) { } grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - - clean: { - options: { - force: true - }, - build: packageFile.build.dest - }, - mkdir: { - build: { - options: { - create: [packageFile.build.dest] - }, - }, - }, - copy: { - main: { - expand: true, - src: packageFile.build.src, - dest: packageFile.build.dest - } - }, + clean: packageFile.grunt.clean, + mkdir: packageFile.grunt.mkdir, + copy: packageFile.grunt.copy, comments: { js: { options: { @@ -83,9 +106,9 @@ module.exports = function (grunt) { options: { position: 'top', banner: '/*\n' + - ' * (c) Copyright Ascensio System Limited 2010-<%= grunt.template.today("yyyy") %>. All rights reserved\n' + + ' * Copyright (C) ' + process.env['PUBLISHER_NAME'] + ' 2012-<%= grunt.template.today("yyyy") %>. All rights reserved\n' + ' *\n' + - ' * <%= pkg.homepage %> \n' + + ' * ' + process.env['PUBLISHER_URL'] + ' \n' + ' *\n' + ' * Version: ' + process.env['PRODUCT_VERSION'] + ' (build:' + process.env['BUILD_NUMBER'] + ')\n' + ' */\n', @@ -98,6 +121,12 @@ module.exports = function (grunt) { }, checkDependencies: checkDependencies }); + + grunt.registerTask('build-develop', 'Build develop scripts', function () { + grunt.initConfig({ + copy: packageFile.grunt["develop-copy"] + }); + }); grunt.loadNpmTasks('grunt-contrib-clean'); grunt.loadNpmTasks('grunt-contrib-copy'); @@ -107,5 +136,5 @@ module.exports = function (grunt) { grunt.loadNpmTasks('grunt-check-dependencies'); grunt.registerTask('default', ['clean', 'mkdir', 'copy', 'comments', 'usebanner', 'checkDependencies']); - -}; \ No newline at end of file + grunt.registerTask('develop', ['build-develop', 'copy']); +}; diff --git a/Makefile b/Makefile index bd8e7dd4fa..e8e1308f9c 100644 --- a/Makefile +++ b/Makefile @@ -1,14 +1,23 @@ -OUTPUT_DIR = build/server -OUTPUT = $(OUTPUT_DIR) - GRUNT = grunt -GRUNT_FLAGS = --no-color -v +GRUNT_FLAGS = --no-color -v GRUNT_FILES = Gruntfile.js.out PRODUCT_VERSION ?= 0.0.0 BUILD_NUMBER ?= 0 +PUBLISHER_NAME ?= Ascensio System SIA +PUBLISHER_URL ?= https://www.onlyoffice.com/ + +GRUNT_ENV += PRODUCT_VERSION=$(PRODUCT_VERSION) +GRUNT_ENV += BUILD_NUMBER=$(BUILD_NUMBER) +GRUNT_ENV += PUBLISHER_NAME="$(PUBLISHER_NAME)" +GRUNT_ENV += PUBLISHER_URL="$(PUBLISHER_URL)" + +BRANDING_DIR ?= ./branding + +DOCUMENT_ROOT ?= /var/www/onlyoffice/documentserver + ifeq ($(OS),Windows_NT) PLATFORM := win EXEC_EXT := .exe @@ -24,6 +33,7 @@ else ifeq ($(UNAME_S),Linux) PLATFORM := linux SHARED_EXT := .so* + LIB_PREFIX := lib endif UNAME_M := $(shell uname -m) ifeq ($(UNAME_M),x86_64) @@ -36,36 +46,18 @@ endif TARGET := $(PLATFORM)_$(ARCHITECTURE) -FILE_CONVERTER = $(OUTPUT)/FileConverter/bin -FILE_CONVERTER_FILES += ../core/build/lib/$(TARGET)/*$(SHARED_EXT) - -ifeq ($(PLATFORM),linux) -FILE_CONVERTER_FILES += ../core/Common/3dParty/icu/$(TARGET)/build/libicudata$(SHARED_EXT) -FILE_CONVERTER_FILES += ../core/Common/3dParty/icu/$(TARGET)/build/libicuuc$(SHARED_EXT) -FILE_CONVERTER_FILES += ../core/Common/3dParty/v8/$(TARGET)/icudtl_dat.S -endif - -ifeq ($(PLATFORM),win) -FILE_CONVERTER_FILES += ../core/Common/3dParty/icu/$(TARGET)/build/icudt55$(SHARED_EXT) -FILE_CONVERTER_FILES += ../core/Common/3dParty/icu/$(TARGET)/build/icuuc55$(SHARED_EXT) -FILE_CONVERTER_FILES += ../core/Common/3dParty/v8/$(TARGET)/release/icudt.dll -endif +OUTPUT = ../build_tools/out/$(TARGET)/onlyoffice/documentserver/server -FILE_CONVERTER_FILES += ../core/build/bin/$(TARGET)/x2t$(EXEC_EXT) - -HTML_FILE_INTERNAL := $(FILE_CONVERTER)/HtmlFileInternal -HTML_FILE_INTERNAL_FILES += ../core/build/lib/$(TARGET)/HtmlFileInternal$(EXEC_EXT) -HTML_FILE_INTERNAL_FILES += ../core/Common/3dParty/cef/$(TARGET)/build/** - -SPELLCHECKER_DICTIONARIES := $(OUTPUT)/SpellChecker/dictionaries -SPELLCHECKER_DICTIONARY_FILES += ../dictionaries/** +SPELLCHECKER_DICTIONARIES := $(OUTPUT)/../dictionaries +SPELLCHECKER_DICTIONARY_FILES += ../dictionaries/*_* SCHEMA_DIR = schema SCHEMA_FILES = $(SCHEMA_DIR)/** SCHEMA = $(OUTPUT)/$(SCHEMA_DIR)/ TOOLS_DIR = tools -TOOLS_FILES = ../core/build/bin/AllFontsGen/$(TARGET) +TOOLS_FILES += ../core/build/bin/$(TARGET)/allfontsgen$(EXEC_EXT) +TOOLS_FILES += ../core/build/bin/$(TARGET)/allthemesgen$(EXEC_EXT) TOOLS = $(OUTPUT)/$(TOOLS_DIR) LICENSE_FILES = LICENSE.txt 3rd-Party.txt license/ @@ -75,26 +67,34 @@ LICENSE_JS := $(OUTPUT)/Common/sources/license.js COMMON_DEFINES_JS := $(OUTPUT)/Common/sources/commondefines.js WELCOME_DIR = welcome -WELCOME_FILES = $(WELCOME_DIR)/** +WELCOME_FILES = $(BRANDING_DIR)/$(WELCOME_DIR)/** WELCOME = $(OUTPUT)/$(WELCOME_DIR)/ -.PHONY: all clean install uninstall build-date htmlfileinternal +INFO_DIR = info +INFO_FILES = $(BRANDING_DIR)/$(INFO_DIR)/** +INFO = $(OUTPUT)/$(INFO_DIR)/ + +CORE_FONTS_DIR = core-fonts +CORE_FONTS_FILES = ../$(CORE_FONTS_DIR)/** +CORE_FONTS = $(OUTPUT)/../$(CORE_FONTS_DIR)/ + +DOCUMENT_TEMPLATES_DIR = document-templates +DOCUMENT_TEMPLATES_FILES = ../$(DOCUMENT_TEMPLATES_DIR)/** +DOCUMENT_TEMPLATES = $(OUTPUT)/../$(DOCUMENT_TEMPLATES_DIR)/ + +DEBUG = $(BRANDING_DIR)/debug.js + +.PHONY: all clean install uninstall build-date .NOTPARALLEL: -all: $(FILE_CONVERTER) $(SPELLCHECKER_DICTIONARIES) $(TOOLS) $(SCHEMA) $(LICENSE) $(WELCOME) build-date +all: $(SPELLCHECKER_DICTIONARIES) $(TOOLS) $(SCHEMA) $(CORE_FONTS) $(DOCUMENT_TEMPLATES) $(LICENSE) $(WELCOME) $(INFO) build-date build-date: $(GRUNT_FILES) sed "s|\(const buildVersion = \).*|\1'${PRODUCT_VERSION}';|" -i $(COMMON_DEFINES_JS) sed "s|\(const buildNumber = \).*|\1${BUILD_NUMBER};|" -i $(COMMON_DEFINES_JS) sed "s|\(const buildDate = \).*|\1'$$(date +%F)';|" -i $(LICENSE_JS) - -htmlfileinternal: $(FILE_CONVERTER) - mkdir -p $(HTML_FILE_INTERNAL) && \ - cp -r -t $(HTML_FILE_INTERNAL) $(HTML_FILE_INTERNAL_FILES) - -$(FILE_CONVERTER): $(GRUNT_FILES) - mkdir -p $(FILE_CONVERTER) && \ - cp -r -t $(FILE_CONVERTER) $(FILE_CONVERTER_FILES) + test -e $(DEBUG) && \ + cp $(DEBUG) $(OUTPUT)/Common/sources || true $(SPELLCHECKER_DICTIONARIES): $(GRUNT_FILES) mkdir -p $(SPELLCHECKER_DICTIONARIES) && \ @@ -103,75 +103,86 @@ $(SPELLCHECKER_DICTIONARIES): $(GRUNT_FILES) $(SCHEMA): mkdir -p $(SCHEMA) && \ cp -r -t $(SCHEMA) $(SCHEMA_FILES) - + $(TOOLS): mkdir -p $(TOOLS) && \ - cp -r -t $(TOOLS) $(TOOLS_FILES) && \ - mv $(TOOLS)/$(TARGET)$(EXEC_EXT) $(TOOLS)/AllFontsGen$(EXEC_EXT) - + cp -r -t $(TOOLS) $(TOOLS_FILES) + $(LICENSE): mkdir -p $(OUTPUT) && \ cp -r -t $(OUTPUT) $(LICENSE_FILES) - + $(GRUNT_FILES): cd $(@D) && \ npm install && \ - $(GRUNT) $(GRUNT_FLAGS) + $(GRUNT_ENV) $(GRUNT) $(GRUNT_FLAGS) + mkdir -p $(OUTPUT) + cp -r -t $(OUTPUT) ./build/server/* echo "Done" > $@ $(WELCOME): mkdir -p $(WELCOME) && \ cp -r -t $(WELCOME) $(WELCOME_FILES) +$(INFO): + mkdir -p $(INFO) && \ + cp -r -t $(INFO) $(INFO_FILES) + +$(CORE_FONTS): + mkdir -p $(CORE_FONTS) && \ + cp -r -t $(CORE_FONTS) $(CORE_FONTS_FILES) + +$(DOCUMENT_TEMPLATES): + mkdir -p $(DOCUMENT_TEMPLATES) && \ + cp -r -t $(DOCUMENT_TEMPLATES) $(DOCUMENT_TEMPLATES_FILES) + clean: - rm -rf $(OUTPUT) $(GRUNT_FILES) + rm -rf $(GRUNT_FILES) install: - sudo adduser --quiet --home /var/www/onlyoffice --system --group onlyoffice - - sudo mkdir -p /var/www/onlyoffice/documentserver - sudo mkdir -p /var/log/onlyoffice/documentserver - sudo mkdir -p /var/lib/onlyoffice/documentserver/App_Data - - sudo cp -fr -t /var/www/onlyoffice/documentserver build/* ../web-apps/deploy/* - sudo mkdir -p /etc/onlyoffice/documentserver - sudo mv /var/www/onlyoffice/documentserver/server/Common/config/* /etc/onlyoffice/documentserver - - sudo chown onlyoffice:onlyoffice -R /var/www/onlyoffice - sudo chown onlyoffice:onlyoffice -R /var/log/onlyoffice - sudo chown onlyoffice:onlyoffice -R /var/lib/onlyoffice - - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libDjVuFile.so /lib/libDjVuFile.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libdoctrenderer.so /lib/libdoctrenderer.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libHtmlFile.so /lib/libHtmlFile.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libHtmlRenderer.so /lib/libHtmlRenderer.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libPdfReader.so /lib/libPdfReader.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libPdfWriter.so /lib/libPdfWriter.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libXpsFile.so /lib/libXpsFile.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libUnicodeConverter.so /lib/libUnicodeConverter.so - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libicudata.so.55 /lib/libicudata.so.55 - sudo ln -s /var/www/onlyoffice/documentserver/server/FileConverter/bin/libicuuc.so.55 /lib/libicuuc.so.55 - - sudo -u onlyoffice "/var/www/onlyoffice/documentserver/server/tools/AllFontsGen"\ - "/usr/share/fonts"\ - "/var/www/onlyoffice/documentserver/sdkjs/common/AllFonts.js"\ - "/var/www/onlyoffice/documentserver/sdkjs/common/Images"\ - "/var/www/onlyoffice/documentserver/server/FileConverter/bin/font_selection.bin" + mkdir -pv ${DESTDIR}/var/www/onlyoffice + if ! id -u onlyoffice > /dev/null 2>&1; then useradd -m -d /var/www/onlyoffice -r -U onlyoffice; fi + + mkdir -p ${DESTDIR}${DOCUMENT_ROOT}/fonts + mkdir -p ${DESTDIR}/var/log/onlyoffice/documentserver + mkdir -p ${DESTDIR}/var/lib/onlyoffice/documentserver/App_Data + + cp -fr -t ${DESTDIR}${DOCUMENT_ROOT} build/* ../web-apps/deploy/* + mkdir -p ${DESTDIR}/etc/onlyoffice/documentserver + mv ${DESTDIR}${DOCUMENT_ROOT}/server/Common/config/* ${DESTDIR}/etc/onlyoffice/documentserver + + chown onlyoffice:onlyoffice -R ${DESTDIR}/var/www/onlyoffice + chown onlyoffice:onlyoffice -R ${DESTDIR}/var/log/onlyoffice + chown onlyoffice:onlyoffice -R ${DESTDIR}/var/lib/onlyoffice + + # Make symlinks for shared libs + find \ + ${DESTDIR}${DOCUMENT_ROOT}/server/FileConverter/bin \ + -maxdepth 1 \ + -name *$(SHARED_EXT) \ + -exec sh -c 'ln -sf {} ${DESTDIR}/lib/$$(basename {})' \; + + sudo -u onlyoffice "${DESTDIR}${DOCUMENT_ROOT}/server/tools/allfontsgen"\ + --input="${DESTDIR}${DOCUMENT_ROOT}/core-fonts"\ + --allfonts-web="${DESTDIR}${DOCUMENT_ROOT}/sdkjs/common/AllFonts.js"\ + --allfonts="${DESTDIR}${DOCUMENT_ROOT}/server/FileConverter/bin/AllFonts.js"\ + --images="${DESTDIR}${DOCUMENT_ROOT}/sdkjs/common/Images"\ + --selection="${DESTDIR}${DOCUMENT_ROOT}/server/FileConverter/bin/font_selection.bin"\ + --output-web="${DESTDIR}${DOCUMENT_ROOT}/fonts"\ + --use-system="true" + + sudo -u onlyoffice "${DESTDIR}${DOCUMENT_ROOT}/server/tools/allthemesgen"\ + --converter-dir="${DESTDIR}${DOCUMENT_ROOT}/server/FileConverter/bin"\ + --src="${DESTDIR}${DOCUMENT_ROOT}/sdkjs/slide/themes"\ + --output="${DESTDIR}${DOCUMENT_ROOT}/sdkjs/common/Images" + uninstall: - sudo userdel onlyoffice - - sudo unlink /lib/libDjVuFile.so - sudo unlink /lib/libdoctrenderer.so - sudo unlink /lib/libHtmlFile.so - sudo unlink /lib/libHtmlRenderer.so - sudo unlink /lib/libPdfReader.so - sudo unlink /lib/libPdfWriter.so - sudo unlink /lib/libXpsFile.so - sudo unlink /lib/libUnicodeConverter.so - sudo unlink /lib/libicudata.so.55 - sudo unlink /lib/libicuuc.so.55 - - sudo rm -rf /var/www/onlyoffice/documentserver - sudo rm -rf /var/log/onlyoffice/documentserver - sudo rm -rf /var/lib/onlyoffice/documentserver - sudo rm -rf /etc/onlyoffice/documentserver + userdel onlyoffice + + # Unlink installed shared libs + find /lib -type l | while IFS= read -r lnk; do if (readlink "$$lnk" | grep -q '^${DOCUMENT_ROOT}/server/FileConverter/bin/'); then rm "$$lnk"; fi; done + + rm -rf /var/www/onlyoffice/documentserver + rm -rf /var/log/onlyoffice/documentserver + rm -rf /var/lib/onlyoffice/documentserver + rm -rf /etc/onlyoffice/documentserver diff --git a/Metrics/config/config.js b/Metrics/config/config.js index f134c6d4da..4f9150fb01 100644 --- a/Metrics/config/config.js +++ b/Metrics/config/config.js @@ -1,5 +1,5 @@ /* - * (c) Copyright Ascensio System SIA 2010-2017 + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under diff --git a/Metrics/npm-shrinkwrap.json b/Metrics/npm-shrinkwrap.json new file mode 100644 index 0000000000..85cae434a5 --- /dev/null +++ b/Metrics/npm-shrinkwrap.json @@ -0,0 +1,92 @@ +{ + "name": "metrics", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "commander": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-1.3.1.tgz", + "integrity": "sha1-AkQ+AtuW9LMrZ0IlRRq7bpUQAA4=", + "optional": true, + "requires": { + "keypress": "0.1.x" + } + }, + "connection-parse": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/connection-parse/-/connection-parse-0.0.7.tgz", + "integrity": "sha1-GOcxiqsGppkmc3KxDFIm0locmmk=", + "optional": true + }, + "generic-pool": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.2.0.tgz", + "integrity": "sha1-i0ZcGnWI6p3SuxM72gu2a/74pj4=" + }, + "hashring": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/hashring/-/hashring-3.2.0.tgz", + "integrity": "sha1-/aTv3oqiLNuX+x0qZeiEAeHBRM4=", + "optional": true, + "requires": { + "connection-parse": "0.0.x", + "simple-lru-cache": "0.0.x" + } + }, + "keypress": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/keypress/-/keypress-0.1.0.tgz", + "integrity": "sha1-SjGI1CkbZrT2XtuZ+AaqmuKTWSo=", + "optional": true + }, + "modern-syslog": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/modern-syslog/-/modern-syslog-1.2.0.tgz", + "integrity": "sha512-dmFE23qpyZJf8MOdzuNKliW4j1PCqxaRtSzyNnv6QDUWjf1z8T4ZoQ7Qf0t6It2ewNv9/XJZSJoUgwpq3D0X7A==", + "optional": true, + "requires": { + "nan": "^2.13.2" + } + }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "optional": true + }, + "sequence": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/sequence/-/sequence-2.2.1.tgz", + "integrity": "sha1-f1YXiV1ENRwKBH52RGdpBJChawM=", + "optional": true + }, + "simple-lru-cache": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz", + "integrity": "sha1-1ZzDoZPBpdAyD4Tucy9uRxPlEd0=", + "optional": true + }, + "statsd": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/statsd/-/statsd-0.8.4.tgz", + "integrity": "sha512-GLxev8J5AtlQILyT2/ofRVgGIbMMu2dMBwcQaEg3BIPZwQQlIG48OYjVyjxo7yI/1AKur97LnJKvjELV6Elx+A==", + "requires": { + "generic-pool": "2.2.0", + "hashring": "3.2.0", + "modern-syslog": "1.2.0", + "winser": "=0.1.6" + } + }, + "winser": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/winser/-/winser-0.1.6.tgz", + "integrity": "sha1-CGY9wyh4oSu84WLYQNpQl7SEZsk=", + "optional": true, + "requires": { + "commander": "1.3.1", + "sequence": "2.2.1" + } + } + } +} diff --git a/Metrics/package.json b/Metrics/package.json index 59e0f407f8..9dbd339c15 100644 --- a/Metrics/package.json +++ b/Metrics/package.json @@ -1,9 +1,19 @@ { "name": "metrics", "version": "1.0.0", - "homepage": "http://www.onlyoffice.com", + "homepage": "https://www.onlyoffice.com", "private": true, + "bin": "node_modules/statsd/bin/statsd", "dependencies": { - "statsd": "^0.7.2" + "statsd": "0.8.4" + }, + "pkg": { + "scripts": [ + "node_modules/statsd/backends/console.js", + "node_modules/statsd/backends/graphite.js", + "node_modules/statsd/backends/repeater.js", + "node_modules/statsd/servers/tcp.js", + "node_modules/statsd/servers/udp.js" + ] } } diff --git a/Readme.md b/Readme.md index b86a404b2c..a7a3adfdcf 100644 --- a/Readme.md +++ b/Readme.md @@ -1,9 +1,9 @@ -[![License](https://img.shields.io/badge/License-GNU%20AGPL%20V3-green.svg?style=flat)](http://www.gnu.org/licenses/agpl-3.0.ru.html) +# Server +[![License](https://img.shields.io/badge/License-GNU%20AGPL%20V3-green.svg?style=flat)](https://www.gnu.org/licenses/agpl-3.0.en.html) -## Server -The backend server software layer which is the part of [ONLYOFFICE Document Server][2] and is the base for all other components. +The backend server software layer which is the part of [ONLYOFFICE Document Server][2] and [ONLYOFFICE Desktop Editors][4] and is the base for all other components. ## Document service set up @@ -13,60 +13,68 @@ This instruction describes document service deployment for Windows based platfor For the document service to work correctly it is necessary to install the following components for your Windows system (if not specified additionally, the latest version for 32 or 64 bit Windows can be installed with default settings): -a) Node.js version 6.9.1 or later (https://nodejs.org/en/download/) +1. [Node.js](https://nodejs.org/en/download/) version 8.0.0 or later -b) Java (https://java.com/en/download/). Necessary for the sdk build. +2. [Java](https://java.com/en/download/). Necessary for the sdk build. -c) MySql Server version 5.5 or later (http://dev.mysql.com/downloads/windows/installer/). When installing use the `onlyoffice` password for the `root` user. -Ñ) or PostgreSQL Server version 9.1 or later (https://www.postgresql.org/download/). +3. Database (MySQL or PostgreSQL). When installing use the `onlyoffice` password for the `root` user. + * [MySQL Server](http://dev.mysql.com/downloads/windows/installer/) version 5.5 or later -d) Erlang (http://www.erlang.org/download.html) + * [PostgreSQL Server](https://www.postgresql.org/download/) version 9.1 or later -e) RabbitMQ (https://www.rabbitmq.com/releases/rabbitmq-server/v3.5.4/rabbitmq-server-3.5.4.exe) +4. [Erlang](https://www.erlang.org/download.html) -f) Redis (https://github.com/MSOpenTech/redis/releases/download/win-2.8.2102/Redis-x64-2.8.2102.msi) +5. [RabbitMQ](https://www.rabbitmq.com/releases/rabbitmq-server/v3.5.4/rabbitmq-server-3.5.4.exe) -g) Python 2.7.x (http://www.python.org/download/releases/2.7.3/#download) +6. [Redis](https://github.com/microsoftarchive/redis/releases/latest) -h) Microsoft Visual C++ Express 2010 (necessary for the spellchecker modules build) +7. [Python 2.7](https://www.python.org/downloads/release/python-2716/) -### Setting up the system +8. Microsoft Visual C++ Express 2010 (necessary for the spellchecker modules build) -a) Database setup MySQL +### Setting up the system -Run the schema/mysql/createdb.sql script for MySQL +1. Database setup: -a) or Database setup PostgreSQL -Enter in `psql` (PostgreSQL interactive terminal) with login and password introduced during installation, then enter commands: -`CREATE DATABASE onlyoffice;` -`CREATE USER onlyoffice WITH PASSWORD 'onlyoffice';` -`\c onlyoffice` -`\i 'schema/postgresql/createdb.sql';` -`GRANT ALL PRIVILEGES ON DATABASE onlyoffice to onlyoffice;` -`GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO onlyoffice;` -Delete from `server\Common\config\development-windows.json` option `sql`. + * Database setup for MySQL + Run the `schema/mysql/createdb.sql` script for MySQL -b) Install the Web Monitor for RabbitMQ (see the details for the installation here - https://www.rabbitmq.com/management.html) + * Database setup for PostgreSQL + 1. Enter in `psql` (PostgreSQL interactive terminal) with + login and password introduced during installation, then enter commands: -Open the command line `cmd` executable. Switch to the installation directory using the `cd /d Installation-directory/sbin` command. + ```sql + CREATE USER onlyoffice WITH PASSWORD 'onlyoffice'; + CREATE DATABASE onlyoffice OWNER onlyoffice; + \c onlyoffice + \i 'schema/postgresql/createdb.sql'; + ``` -Run the following command: + 2. Delete from `server\Common\config\development-windows.json` option `sql`. -``` -rabbitmq-plugins.bat enable rabbitmq_management -``` +2. Install the Web Monitor for RabbitMQ (see the details for the installation [here](https://www.rabbitmq.com/management.html)) +3. Open the command line `cmd` executable. +4. Switch to the installation directory using the `cd /d Installation-directory/sbin` command. +5. Run the following command: -The Web Monitor is located at the http://localhost:15672/ address. Use the `guest/guest` for the login/password combination. + ```powershell + rabbitmq-plugins.bat enable rabbitmq_management + ``` -c) If Redis does not start or crashes after the start for some reason, try to change the `maxheap` parameter in the config settings. For 64 bit version of Windows 7 the config file can be found here: C:\Program Files\Redis\redis.windows-service.conf. +6. The Web Monitor is located at the [http://localhost:15672/](http://localhost:15672/) address. + Use the `guest:guest` for the login:password combination. -Find the `# maxheap ` line and change it to, e.g. +7. If Redis does not start or crashes after the start for some reason, + try to change the `maxheap` parameter in the config settings. + For 64 bit version of Windows 7 the config file can be found here: + `C:\Program Files\Redis\redis.windows-service.conf`. + Find the `# maxheap ` line and change it to, e.g. -``` -maxheap 128MB -``` + ```config + maxheap 128MB + ``` -Restart the service. + and restart the service ### Running the service @@ -74,21 +82,23 @@ Run the `run.bat` script to start the service. Notes -All config files for the server part can be foun in the `Common\config` folder +All config files for the server part can be found in the `Common\config` folder + * `default.json` - common config files similar for all production versions. * `production-windows.json` - config files for the production version running on a Windows based platform. * `production-linux.json` - config files for the production version running on a Linux based platform. * `development-windows.json` - config files for the development version running on a Windows based platform (this configuration is used when running the 'run.bat' script). -In case it is necessary to temporarily edit the config files, create the local.json file and reassign the values there. It will allow to prevent from uploading local changes and losing config files when updating the repository. See https://github.com/lorenwest/node-config/wiki/Configuration-Files for more information about the configuration files. +In case it is necessary to temporarily edit the config files, create the local.json file and reassign the values there. It will allow to prevent from uploading local changes and losing config files when updating the repository. See [Configuration Files](https://github.com/lorenwest/node-config/wiki/Configuration-Files) for more information about the configuration files. ## User Feedback and Support -If you have any problems with or questions about [ONLYOFFICE Document Server][2], please visit our official forum to find answers to your questions: [dev.onlyoffice.org][1] or you can ask and answer ONLYOFFICE development questions on [Stack Overflow][3]. +If you have any problems with or questions about [ONLYOFFICE Document Server][2], please visit our official forum to find answers to your questions: [forum.onlyoffice.com][1] or you can ask and answer ONLYOFFICE development questions on [Stack Overflow][3]. - [1]: http://dev.onlyoffice.org + [1]: https://forum.onlyoffice.com [2]: https://github.com/ONLYOFFICE/DocumentServer - [3]: http://stackoverflow.com/questions/tagged/onlyoffice + [3]: https://stackoverflow.com/questions/tagged/onlyoffice + [4]: https://github.com/ONLYOFFICE/DesktopEditors ## License diff --git a/SpellChecker/npm-shrinkwrap.json b/SpellChecker/npm-shrinkwrap.json new file mode 100644 index 0000000000..7cd0edf114 --- /dev/null +++ b/SpellChecker/npm-shrinkwrap.json @@ -0,0 +1,590 @@ +{ + "name": "spellchecker", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "requires": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" + }, + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "config": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/config/-/config-2.0.1.tgz", + "integrity": "sha512-aTaviJnC8ZjQYx8kQf4u6tWqIxWolyQQ3LqXgnCLAsIb78JrUshHG0YuzIarzTaVVe1Pazms3TXImfYra8UsyQ==", + "requires": { + "json5": "^1.0.1" + } + }, + "content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "requires": { + "safe-buffer": "5.2.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "requires": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "finalhandler": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "http-errors": { + "version": "2.0.0", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "http-parser-js": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.3.tgz", + "integrity": "sha512-t7hjvef/5HEK7RWTdUzVUhl8zkEu+LlaE0IYzdMuvbSDipxBRpOn4Uhw8ZyECEa808iVT8XCjzo6xmYt4CiLZg==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "json5": { + "version": "1.0.1", + "resolved": "http://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + }, + "node-addon-api": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.2.tgz", + "integrity": "sha512-+D4s2HCnxPd5PjjI0STKwncjXTUKKqm74MDMz9OPXavjsGmjkvwgLtA5yoxJUdmpj52+2u+RrXgPipahKczMKg==" + }, + "nodehun": { + "version": "git+https://git@github.com/ONLYOFFICE/nodehun.git#2411a56828c7d58214c61781b4a5c63d18adba99", + "from": "git+https://git@github.com/ONLYOFFICE/nodehun.git#2411a56828c7d58214c61781b4a5c63d18adba99", + "requires": { + "node-addon-api": "*" + } + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "requires": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + } + }, + "sockjs": { + "version": "0.3.21", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.21.tgz", + "integrity": "sha512-DhbPFGpxjc6Z3I+uX07Id5ZO2XwYsWOrYjaSeieES78cq+JaJvVe5q/m1uvjIQhXinhIeCFRH6JgXe+mvVMyXw==", + "requires": { + "faye-websocket": "^0.11.3", + "uuid": "^3.4.0", + "websocket-driver": "^0.7.4" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" + }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + } + } +} diff --git a/SpellChecker/package.json b/SpellChecker/package.json index b6d85f66a1..62d2d250b5 100644 --- a/SpellChecker/package.json +++ b/SpellChecker/package.json @@ -1,12 +1,19 @@ -{ - "name": "spellchecker", - "version": "1.0.1", - "homepage": "http://www.onlyoffice.com", - "private": true, - "dependencies": { - "express" : "^4.14.0", - "sockjs" : "^0.3.17", - "nodehun" : "git+https://git@github.com/ONLYOFFICE/nodehun.git", - "config" : "^1.21.0" - } -} \ No newline at end of file +{ + "name": "spellchecker", + "version": "1.0.1", + "homepage": "https://www.onlyoffice.com", + "private": true, + "bin": "sources/server.js", + "dependencies": { + "co": "4.6.0", + "config": "2.0.1", + "express": "4.19.2", + "nodehun": "git+https://git@github.com/ONLYOFFICE/nodehun.git#2411a56828c7d58214c61781b4a5c63d18adba99", + "sockjs": "0.3.21" + }, + "pkg": { + "assets": [ + "dictionaries/**/*" + ] + } +} diff --git a/SpellChecker/sources/languages.js b/SpellChecker/sources/languages.js new file mode 100644 index 0000000000..e4f1b05fcf --- /dev/null +++ b/SpellChecker/sources/languages.js @@ -0,0 +1,105 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +"use strict"; + +const idLanguages = [0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, + 0x000d, 0x000e, 0x000f, 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, 0x0018, 0x0019, 0x001a, + 0x001b, 0x001c, 0x001d, 0x001e, 0x001f, 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, + 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0032, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, + 0x003a, 0x003b, 0x003c, 0x003e, 0x003f, 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, + 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0056, 0x0057, + 0x005a, 0x005b, 0x005d, 0x005e, 0x005f, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0068, 0x006a, 0x006b, 0x006c, + 0x006d, 0x006e, 0x006f, 0x0070, 0x0078, 0x007a, 0x007c, 0x007e, 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, + 0x0086, 0x0087, 0x0088, 0x008c, 0x0091, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, 0x0408, 0x0409, + 0x040a, 0x040b, 0x040c, 0x040d, 0x040e, 0x040f, 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, 0x041f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, + 0x0426, 0x0427, 0x0428, 0x0429, 0x042a, 0x042b, 0x042c, 0x042d, 0x042e, 0x042f, 0x0430, 0x0431, 0x0432, 0x0433, + 0x0434, 0x0435, 0x0436, 0x0437, 0x0438, 0x0439, 0x043a, 0x043b, 0x043e, 0x043f, 0x0440, 0x0441, 0x0442, 0x0443, + 0x0444, 0x0445, 0x0446, 0x0447, 0x0448, 0x0449, 0x044a, 0x044b, 0x044c, 0x044d, 0x044e, 0x044f, 0x0450, 0x0451, + 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, 0x0458, 0x0459, 0x045a, 0x045b, 0x045c, 0x045d, 0x045e, 0x045f, + 0x0461, 0x0462, 0x0463, 0x0464, 0x0465, 0x0466, 0x0467, 0x0468, 0x0469, 0x046a, 0x046b, 0x046c, 0x046d, 0x046e, + 0x046f, 0x0470, 0x0471, 0x0472, 0x0473, 0x0474, 0x0475, 0x0477, 0x0478, 0x0479, 0x047a, 0x047c, 0x047e, 0x0480, + 0x0481, 0x0482, 0x0483, 0x0484, 0x0485, 0x0486, 0x0487, 0x0488, 0x048c, 0x048d, 0x0491, 0x0801, 0x0803, 0x0804, + 0x0807, 0x0809, 0x080a, 0x080c, 0x0810, 0x0813, 0x0814, 0x0816, 0x0818, 0x0819, 0x081a, 0x081d, 0x0820, 0x082c, + 0x082e, 0x083b, 0x083c, 0x083e, 0x0843, 0x0845, 0x0846, 0x0850, 0x0851, 0x0859, 0x085d, 0x085f, 0x0861, 0x086b, + 0x0873, 0x0c01, 0x0c04, 0x0c07, 0x0c09, 0x0c0a, 0x0c0c, 0x0c1a, 0x0c3b, 0x0c5f, 0x0c6b, 0x1001, 0x1004, 0x1007, + 0x1009, 0x100a, 0x100c, 0x101a, 0x103b, 0x1401, 0x1404, 0x1407, 0x1409, 0x140a, 0x140c, 0x141a, 0x143b, 0x1801, + 0x1809, 0x180a, 0x180c, 0x181a, 0x183b, 0x1c01, 0x1c09, 0x1c0a, 0x1c0c, 0x1c1a, 0x1c3b, 0x2001, 0x2009, 0x200a, + 0x200c, 0x201a, 0x203b, 0x2401, 0x2409, 0x240a, 0x240c, 0x241a, 0x243b, 0x2801, 0x2809, 0x280a, 0x280c, 0x281a, + 0x2c01, 0x2c09, 0x2c0a, 0x2c0c, 0x2c1a, 0x3001, 0x3009, 0x300a, 0x300c, 0x301a, 0x3401, 0x3409, 0x340a, 0x340c, + 0x3801, 0x3809, 0x380a, 0x380c, 0x3c01, 0x3c09, 0x3c0a, 0x3c0c, 0x4001, 0x4009, 0x400a, 0x4409, 0x440a, 0x4809, + 0x480a, 0x4c0a, 0x500a, 0x540a, 0x641a, 0x681a, 0x6c1a, 0x701a, 0x703b, 0x742c, 0x743b, 0x7804, 0x7814, 0x781a, + 0x782c, 0x783b, 0x7843, 0x7850, 0x785d, 0x7c04, 0x7c14, 0x7c1a, 0x7c28, 0x7c2e, 0x7c3b, 0x7c43, 0x7c50, 0x7c5d, + 0x7c5f, 0x7c68]; +const sLanguages = ['ar', 'bg', 'ca', 'zh_Hans', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr', 'he', 'hu', 'is', + 'it', 'ja', 'ko', 'nl', 'no', 'pl', 'pt', 'rm', 'ro', 'ru', 'hr', 'sk', 'sq', 'sv', 'th', 'tr', 'ur', 'id', 'uk', + 'be', 'sl', 'et', 'lv', 'lt', 'tg', 'fa', 'vi', 'hy', 'az', 'eu', 'hsb', 'mk', 'tn', 'xh', 'zu', 'af', 'ka', 'fo', + 'hi', 'mt', 'se', 'ga', 'ms', 'kk', 'ky', 'sw', 'tk', 'uz', 'tt', 'bn', 'pa', 'gu', 'or', 'ta', 'te', 'kn', 'ml', + 'as', 'mr', 'sa', 'mn', 'bo', 'cy', 'km', 'lo', 'gl', 'kok', 'syr', 'si', 'iu', 'am', 'tzm', 'ne', 'fy', 'ps', + 'fil', 'dv', 'ha', 'yo', 'quz', 'nso', 'ba', 'lb', 'kl', 'ig', 'ii', 'arn', 'moh', 'br', 'ug', 'mi', 'oc', 'co', + 'gsw', 'sah', 'qut', 'rw', 'wo', 'prs', 'gd', 'ar_SA', 'bg_BG', 'ca_ES', 'zh_TW', 'cs_CZ', 'da_DK', 'de_DE', + 'el_GR', 'en_US', 'es_ES_tradnl', 'fi_FI', 'fr_FR', 'he_IL', 'hu_HU', 'is_IS', 'it_IT', 'ja_JP', 'ko_KR', 'nl_NL', + 'nb_NO', 'pl_PL', 'pt_BR', 'rm_CH', 'ro_RO', 'ru_RU', 'hr_HR', 'sk_SK', 'sq_AL', 'sv_SE', 'th_TH', 'tr_TR', 'ur_PK', + 'id_ID', 'uk_UA', 'be_BY', 'sl_SI', 'et_EE', 'lv_LV', 'lt_LT', 'tg_Cyrl_TJ', 'fa_IR', 'vi_VN', 'hy_AM', + 'az_Latn_AZ', 'eu_ES', 'wen_DE', 'mk_MK', 'st_ZA', 'ts_ZA', 'tn_ZA', 'ven_ZA', 'xh_ZA', 'zu_ZA', 'af_ZA', 'ka_GE', + 'fo_FO', 'hi_IN', 'mt_MT', 'se_NO', 'ms_MY', 'kk_KZ', 'ky_KG', 'sw_KE', 'tk_TM', 'uz_Latn_UZ', 'tt_RU', 'bn_IN', + 'pa_IN', 'gu_IN', 'or_IN', 'ta_IN', 'te_IN', 'kn_IN', 'ml_IN', 'as_IN', 'mr_IN', 'sa_IN', 'mn_MN', 'bo_CN', 'cy_GB', + 'km_KH', 'lo_LA', 'my_MM', 'gl_ES', 'kok_IN', 'mni', 'sd_IN', 'syr_SY', 'si_LK', 'chr_US', 'iu_Cans_CA', 'am_ET', + 'tmz', 'ne_NP', 'fy_NL', 'ps_AF', 'fil_PH', 'dv_MV', 'bin_NG', 'fuv_NG', 'ha_Latn_NG', 'ibb_NG', 'yo_NG', 'quz_BO', + 'nso_ZA', 'ba_RU', 'lb_LU', 'kl_GL', 'ig_NG', 'kr_NG', 'gaz_ET', 'ti_ER', 'gn_PY', 'haw_US', 'so_SO', 'ii_CN', + 'pap_AN', 'arn_CL', 'moh_CA', 'br_FR', 'ug_CN', 'mi_NZ', 'oc_FR', 'co_FR', 'gsw_FR', 'sah_RU', 'qut_GT', 'rw_RW', + 'wo_SN', 'prs_AF', 'plt_MG', 'gd_GB', 'ar_IQ', 'ca_ES_valencia', 'zh_CN', 'de_CH', 'en_GB', 'es_MX', 'fr_BE', + 'it_CH', 'nl_BE', 'nn_NO', 'pt_PT', 'ro_MO', 'ru_MO', 'sr_Latn_CS', 'sv_FI', 'ur_IN', 'az_Cyrl_AZ', 'dsb_DE', + 'se_SE', 'ga_IE', 'ms_BN', 'uz_Cyrl_UZ', 'bn_BD', 'pa_PK', 'mn_Mong_CN', 'bo_BT', 'sd_PK', 'iu_Latn_CA', + 'tzm_Latn_DZ', 'ne_IN', 'quz_EC', 'ti_ET', 'ar_EG', 'zh_HK', 'de_AT', 'en_AU', 'es_ES', 'fr_CA', 'sr_Cyrl_CS', + 'se_FI', 'tmz_MA', 'quz_PE', 'ar_LY', 'zh_SG', 'de_LU', 'en_CA', 'es_GT', 'fr_CH', 'hr_BA', 'smj_NO', 'ar_DZ', + 'zh_MO', 'de_LI', 'en_NZ', 'es_CR', 'fr_LU', 'bs_Latn_BA', 'smj_SE', 'ar_MA', 'en_IE', 'es_PA', 'fr_MC', + 'sr_Latn_BA', 'sma_NO', 'ar_TN', 'en_ZA', 'es_DO', 'fr_West', 'sr_Cyrl_BA', 'sma_SE', 'ar_OM', 'en_JM', 'es_VE', + 'fr_RE', 'bs_Cyrl_BA', 'sms_FI', 'ar_YE', 'en_CB', 'es_CO', 'fr_CG', 'sr_Latn_RS', 'smn_FI', 'ar_SY', 'en_BZ', + 'es_PE', 'fr_SN', 'sr_Cyrl_RS', 'ar_JO', 'en_TT', 'es_AR', 'fr_CM', 'sr_Latn_ME', 'ar_LB', 'en_ZW', 'es_EC', + 'fr_CI', 'sr_Cyrl_ME', 'ar_KW', 'en_PH', 'es_CL', 'fr_ML', 'ar_AE', 'en_ID', 'es_UY', 'fr_MA', 'ar_BH', 'en_HK', + 'es_PY', 'fr_HT', 'ar_QA', 'en_IN', 'es_BO', 'en_MY', 'es_SV', 'en_SG', 'es_HN', 'es_NI', 'es_PR', 'es_US', + 'bs_Cyrl', 'bs_Latn', 'sr_Cyrl', 'sr_Latn', 'smn', 'az_Cyrl', 'sms', 'zh', 'nn', 'bs', 'az_Latn', 'sma', 'uz_Cyrl', + 'mn_Cyrl', 'iu_Cans', 'zh_Hant', 'nb', 'sr', 'tg_Cyrl', 'dsb', 'smj', 'uz_Latn', 'mn_Mong', 'iu_Latn', 'tzm_Latn', + 'ha_Latn']; + +const allLanguages = {}; +for (let i = 0; i < idLanguages.length; ++i) { + allLanguages[idLanguages[i]] = sLanguages[i]; +} + +exports.sToId = function (str) { + const index = sLanguages.indexOf(str); + return -1 !== index ? idLanguages[index] : -1; +}; +exports.allLanguages = allLanguages; diff --git a/SpellChecker/sources/server.js b/SpellChecker/sources/server.js index 41f666869f..a6e3a947ce 100644 --- a/SpellChecker/sources/server.js +++ b/SpellChecker/sources/server.js @@ -1,5 +1,5 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 +/* + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -32,17 +32,16 @@ 'use strict'; -var cluster = require('cluster'); -var config = require('config').get('SpellChecker'); +const cluster = require('cluster'); +const config = require('config').get('SpellChecker'); //process.env.NODE_ENV = config.get('server.mode'); -var logger = require('./../../Common/sources/logger'); -var spellCheck; +const logger = require('./../../Common/sources/logger'); -var idCheckInterval, c_nCheckHealth = 60000, c_sCheckWord = 'color', c_sCheckLang = 1033; -var canStartCheck = true; -var statusCheckHealth = true; +const c_nCheckHealth = 60000, c_sCheckWord = 'color', c_sCheckLang = 1033; +let idCheckInterval, canStartCheck = true; +let statusCheckHealth = true; function checkHealth (worker) { logger.info('checkHealth'); if (!statusCheckHealth) { @@ -58,7 +57,7 @@ function endCheckHealth (msg) { statusCheckHealth = true; } -var workersCount = 1; // ToDo Пока только 1 процеÑÑ Ð±ÑƒÐ´ÐµÐ¼ задейÑтвовать. Ðо в будующем Ñтоит раÑÑмотреть неÑколько. +const workersCount = 1; // ToDo So far, we will use only 1 process. But in the future it is worth considering a few. if (cluster.isMaster) { logger.warn('start cluster with %s workers', workersCount); cluster.on('listening', function(worker) { @@ -68,9 +67,8 @@ if (cluster.isMaster) { worker.on('message', function(msg){endCheckHealth(msg);}); } }); - for (var nIndexWorker = 0; nIndexWorker < workersCount; ++nIndexWorker) { - var worker = cluster.fork().process; - logger.warn('worker %s started.', worker.pid); + for (let nIndexWorker = 0; nIndexWorker < workersCount; ++nIndexWorker) { + logger.warn('worker %s started.', cluster.fork().process.pid); } cluster.on('exit', (worker, code, signal) => { @@ -81,32 +79,32 @@ if (cluster.isMaster) { cluster.fork(); }); } else { - var express = require('express'), + const express = require('express'), http = require('http'), https = require('https'), fs = require("fs"), app = express(), - server = null; - spellCheck = require('./spellCheck'); + spellCheck = require('./spellCheck'); + let server = null; + logger.warn('Express server starting...'); if (config.has('ssl')) { - var privateKey = fs.readFileSync(config.get('ssl.key')).toString(); - var certificateKey = fs.readFileSync(config.get('ssl.cert')).toString(); - var trustedCertificate = fs.readFileSync(config.get('ssl.ca')).toString(); + const privateKey = fs.readFileSync(config.get('ssl.key')).toString(); + const certificateKey = fs.readFileSync(config.get('ssl.cert')).toString(); + const trustedCertificate = fs.readFileSync(config.get('ssl.ca')).toString(); //See detailed options format here: http://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener - var options = {key: privateKey, cert: certificateKey, ca: [trustedCertificate]}; + const options = {key: privateKey, cert: certificateKey, ca: [trustedCertificate]}; server = https.createServer(options, app); } else { server = http.createServer(app); } - // ЕÑли захочетÑÑ Ð¸Ñпользовать 'development' и 'production', - // то Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ app.settings.env (https://github.com/strongloop/express/issues/936) - // ЕÑли нужна обработка ошибок, то теперь она Ñ‚Ð°ÐºÐ°Ñ https://github.com/expressjs/errorhandler - spellCheck.install(server, function(){ + // If you want to use 'development' and 'production', + // then with app.settings.env (https://github.com/strongloop/express/issues/936) + // If error handling is needed, now it's like this https://github.com/expressjs/errorhandler spellCheck.install(server, function(){ server.listen(config.get('server.port'), function(){ logger.warn("Express server listening on port %d in %s mode", config.get('server.port'), app.settings.env); }); diff --git a/SpellChecker/sources/spellCheck.js b/SpellChecker/sources/spellCheck.js index 09e4c85c97..22bec333c6 100644 --- a/SpellChecker/sources/spellCheck.js +++ b/SpellChecker/sources/spellCheck.js @@ -1,5 +1,5 @@ -/* - * (c) Copyright Ascensio System SIA 2010-2017 +/* + * (c) Copyright Ascensio System SIA 2010-2024 * * This program is a free software product. You can redistribute it and/or * modify it under the terms of the GNU Affero General Public License (AGPL) @@ -12,8 +12,8 @@ * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html * - * You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, - * EU, LV-1021. + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. * * The interactive user interfaces in modified source and object code versions * of the Program must display Appropriate Legal Notices, as required under @@ -34,48 +34,71 @@ const sockjs = require('sockjs'); const nodehun = require('nodehun'); -const config = require('config').get('SpellChecker'); const logger = require('./../../Common/sources/logger'); +const utils = require('./../../Common/sources/utils'); const fs = require('fs'); -const cfgSockjsUrl = require('config').get('services.CoAuthoring.server.sockjsUrl'); -let arrDictionaries = {}; +const co = require('co'); +const cfgSockjs = require('config').get('services.CoAuthoring.sockjs'); +const languages = require('./languages'); +const allLanguages = languages.allLanguages; +const path = require('path'); +const arrExistDictionaries = {}; +const pathDictionaries = require('config').get('SpellChecker.server.dictDir'); +const arrDictionaries = {}; -(function() { - // Read dictionaries - const arrDictionariesConfig = config.get('dictionaries'); - let oDictTmp = null, pathTmp = '', oDictName = null; - for (let indexDict = 0, lengthDict = arrDictionariesConfig.length; indexDict < lengthDict; ++indexDict) { - oDictTmp = arrDictionariesConfig[indexDict]; - oDictName = oDictTmp.name; - pathTmp = __dirname + '/../dictionaries/' + oDictName + '/' + oDictName + '.'; - arrDictionaries[oDictTmp.id] = new nodehun(pathTmp + 'aff', pathTmp + 'dic'); - } -})(); - -/*function CheckDictionary (dict, correct, unCorrect) { - if (dict) { - dict.isCorrect(correct, function (err, correct, origWord) { - console.log(err, correct, origWord); - if (err || !correct) logger.error('Error: spelling correct word %s failed!', correct); - }); +function spell(type, word, id) { + return new Promise(function(resolve, reject) { + let dict = null; + if (arrDictionaries[id]) { + dict = arrDictionaries[id]; + } else { + if (arrExistDictionaries[id]) { + let pathTmp = path.join(pathDictionaries, allLanguages[id], allLanguages[id] + '.'); + + dict = arrDictionaries[id] = new nodehun(pathTmp + 'aff', pathTmp + 'dic'); + } + } - dict.spellSuggestions(unCorrect, function (err, correct, suggestions, origWord) { - console.log(err, correct, suggestions, origWord); - if (err || correct) logger.error('Error: spelling unCorrect word %s failed!', unCorrect); - }); - } else { - logger.error('Error: no dictionary'); - } + if (dict) { + if ("spell" === type) { + // use setImmediate because https://github.com/nodejs/node/issues/5691 + dict.spell(word) + .then(isCorrect => { + return setImmediate(resolve, isCorrect); + }); + } else if ("suggest" === type) { + dict.suggest(word) + .then(suggestions => { + return setImmediate(resolve, suggestions); + }); + } + } else { + return setImmediate(resolve, true); + } + }); } -CheckDictionary(arrDictionaries[0x0409], 'color', 'calor');*/ exports.install = function (server, callbackFunction) { 'use strict'; - const sockjs_opts = {sockjs_url: cfgSockjsUrl}; - const sockjs_echo = sockjs.createServer(sockjs_opts); + + utils.listFolders(pathDictionaries, true).then((values) => { + return co(function*() { + let lang; + for (let i = 0; i < values.length; ++i) { + lang = languages.sToId(path.basename(values[i])); + if (-1 !== lang) { + arrExistDictionaries[lang] = 1; + } + } + yield spell('spell', 'color', 0x0409); + callbackFunction(); + }); + }); + + const sockjs_echo = sockjs.createServer(cfgSockjs); sockjs_echo.on('connection', function (conn) { - if (null == conn) { + if (!conn) { logger.error ("null == conn"); return; } @@ -83,7 +106,7 @@ exports.install = function (server, callbackFunction) { try { let data = JSON.parse(message); switch (data.type) { - case 'spellCheck': spellCheck(conn, data);break; + case 'spellCheck': spellCheck(conn, data.spellCheckData);break; } } catch (e) { logger.error("error receiving response: %s", e); @@ -95,6 +118,8 @@ exports.install = function (server, callbackFunction) { conn.on('close', function () { logger.info("Connection closed or timed out"); }); + + sendData(conn, {type: 'init', languages: Object.keys(arrExistDictionaries)}); }); function sendData(conn, data) { @@ -102,68 +127,25 @@ exports.install = function (server, callbackFunction) { } function spellCheck(conn, data) { - let oSpellInfo; - function checkEnd() { - if (0 === oSpellInfo.usrWordsLength) { - sendData(conn, { type:"spellCheck", spellCheckData:JSON.stringify(data) }); - } - } - function spellSuggest(index, word, lang) { - oSpellInfo.arrTimes[index] = new Date(); - logger.info('start %s word = %s, lang = %s', data.type, word, lang); - const oDictionary = arrDictionaries[lang]; - if (!oDictionary) { - data.usrCorrect[index] = true; - --data.usrWordsLength; - checkEnd(); - } else if ("spell" === data.type) { - oDictionary.isCorrect(word, function (err, correct, origWord) { - data.usrCorrect[index] = (!err && correct); - logger.info('spell word = %s, lang = %s, time = %s', word, lang, new Date() - oSpellInfo.arrTimes[index]); - --oSpellInfo.usrWordsLength; - checkEnd(); - }); - } else if ("suggest" === data.type) { - oDictionary.spellSuggestions(word, function (err, correct, suggestions, origWord) { - data.usrSuggest[index] = suggestions; - logger.info('suggest word = %s, lang = %s, time = %s', word, lang, new Date() - oSpellInfo.arrTimes[index]); - --oSpellInfo.usrWordsLength; - checkEnd(); - }); + return co(function*() { + let promises = []; + for (let i = 0, length = data.usrWords.length; i < length; ++i) { + promises.push(spell(data.type, data.usrWords[i], data.usrLang[i])); } - } - - data = JSON.parse(data.spellCheckData); - // Ответ - data.usrCorrect = []; - data.usrSuggest = []; - - oSpellInfo = {usrWordsLength: data.usrWords.length, arrTimes: []}; - - //data.start = new Date(); - for (let i = 0, length = data.usrWords.length; i < length; ++i) { - spellSuggest(i, data.usrWords[i], data.usrLang[i]); - } + yield Promise.all(promises).then(values => { + data[('spell' === data.type ? 'usrCorrect' : 'usrSuggest')] = values; + }); + sendData(conn, {type: 'spellCheck', spellCheckData: data}); + }); } sockjs_echo.installHandlers(server, {prefix:'/doc/[0-9-.a-zA-Z_=]*/c', log:function (severity, message) { //TODO: handle severity logger.info(message); }}); - - callbackFunction(); }; exports.spellSuggest = function (type, word, lang, callbackFunction) { - const oDictionary = arrDictionaries[lang]; - if (undefined === oDictionary) { - callbackFunction(false); - } else if ('spell' === type) { - oDictionary.isCorrect(word, function (err, correct, origWord) { - callbackFunction(!err && correct); - }); - } else if ('suggest' === type) { - oDictionary.spellSuggestions(word, function (err, correct, suggestions, origWord) { - callbackFunction(suggestions); - }); - } + return co(function*() { + callbackFunction(yield spell(type, word, lang)); + }); }; diff --git a/welcome/img/favicon.ico b/branding/info/img/favicon.ico similarity index 100% rename from welcome/img/favicon.ico rename to branding/info/img/favicon.ico diff --git a/welcome/img/icon-cross.png b/branding/info/img/icon-cross.png similarity index 100% rename from welcome/img/icon-cross.png rename to branding/info/img/icon-cross.png diff --git a/branding/info/img/logo.png b/branding/info/img/logo.png new file mode 100644 index 0000000000..1abbe5db87 Binary files /dev/null and b/branding/info/img/logo.png differ diff --git a/branding/info/index.html b/branding/info/index.html new file mode 100644 index 0000000000..bcee645c7b --- /dev/null +++ b/branding/info/index.html @@ -0,0 +1,573 @@ + + + + ONLYOFFICEâ„¢ + + + + + + + +
+ ONLYOFFICE +
+
+
+
Please, wait...
+ + +
+
+ + + diff --git a/branding/welcome/img/favicon.ico b/branding/welcome/img/favicon.ico new file mode 100644 index 0000000000..fc55efad2c Binary files /dev/null and b/branding/welcome/img/favicon.ico differ diff --git a/branding/welcome/img/icon-cross.png b/branding/welcome/img/icon-cross.png new file mode 100644 index 0000000000..6401225624 Binary files /dev/null and b/branding/welcome/img/icon-cross.png differ diff --git a/welcome/img/icon-done.png b/branding/welcome/img/icon-done.png similarity index 100% rename from welcome/img/icon-done.png rename to branding/welcome/img/icon-done.png diff --git a/welcome/index.html b/branding/welcome/index.html similarity index 100% rename from welcome/index.html rename to branding/welcome/index.html diff --git a/license/SockJs.license b/license/SockJs.license deleted file mode 100644 index a7c85de14f..0000000000 --- a/license/SockJs.license +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2011-2012 VMware, Inc. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/license/SocketIO.license b/license/SocketIO.license new file mode 100644 index 0000000000..659a042c25 --- /dev/null +++ b/license/SocketIO.license @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2014-2018 Automattic + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/license/Touch.license b/license/Touch.license deleted file mode 100644 index b858bf24c8..0000000000 --- a/license/Touch.license +++ /dev/null @@ -1,68 +0,0 @@ -Sencha Touch & Sencha Touch Charts - JavaScript Libraries -Copyright (c) 2010-2012, Sencha, Inc. -All rights reserved. -licensing@sencha.com - -http://www.sencha.com/products/touch/license.php - - -Open Source License ------------------------------------------------------------------------------------------- -This version of Sencha Touch and Sencha Touch Charts is licensed under the terms of the Open -Source GPL 3.0 license. - -http://www.gnu.org/licenses/gpl.html - -There are several FLOSS exceptions available for use with this release for -open source applications that are distributed under a license other than the GPL. - -* Open Source License Exception for Applications - - http://www.sencha.com/products/floss-exception.php - -* Open Source License Exception for Development - - http://www.sencha.com/products/ux-exception.php - - -Alternate Licensing for Sencha Touch ------------------------------------------------------------------------------------------- -Commercial and OEM Licenses are available for an alternate download of Sencha Touch. -This is the appropriate option if you are creating proprietary applications and you are -not prepared to distribute and share the source code of your application under the -GPL v3 license. Please visit http://www.sencha.com/store/touch/license.php for more details. - - -Alternate Licensing for Sencha Touch Charts ------------------------------------------------------------------------------------------- -Commercial and OEM Licenses are available for an alternate download of Sencha Touch Charts. -This is the appropriate option if you are creating proprietary applications and you are -not prepared to distribute and share the source code of your application under the -GPL v3 license. - -Sencha Touch Charts is available commercially only as a part of Sencha Complete or Sencha -Complete Team. Please visit http://www.sencha.com/products/complete/license or -http://www.sencha.com/products/complete-team/license for more details. - - -Third Party Content ------------------------------------------------------------------------------------------- -The following third party software is distributed with Sencha Touch and is -provided under other licenses and/or has source available from other locations. - -Library: JSON parser -License: Public Domain -Location: http://www.JSON.org/js.html - -Library: flexible-js-formatting - date parsing and formatting -License: MIT -Location: http://code.google.com/p/flexible-js-formatting/ - -Library: Jasmine Å unit testing -License: MIT -Location: https://github.com/pivotal/jasmine - - --- - -THIS SOFTWARE IS DISTRIBUTED "AS-IS" WITHOUT ANY WARRANTIES, CONDITIONS AND REPRESENTATIONS WHETHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, MERCHANTABLE QUALITY, FITNESS FOR A PARTICULAR PURPOSE, DURABILITY, NON-INFRINGEMENT, PERFORMANCE AND THOSE ARISING BY STATUTE OR FROM CUSTOM OR USAGE OF TRADE OR COURSE OF DEALING. \ No newline at end of file diff --git a/license/ZeroClipboard.license b/license/ZeroClipboard.license deleted file mode 100644 index 346a577d7e..0000000000 --- a/license/ZeroClipboard.license +++ /dev/null @@ -1,8 +0,0 @@ -The MIT License (MIT) -Copyright (c) 2012 Joseph Huckaby, Jon Rohan - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/license/jsrsasign.license b/license/jsrsasign.license deleted file mode 100644 index 2c265d8f21..0000000000 --- a/license/jsrsasign.license +++ /dev/null @@ -1,21 +0,0 @@ -The 'jsrsasign'(RSA-Sign JavaScript Library) License - -Copyright (c) 2010-2016 Kenji Urushima - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file diff --git a/license/jszip-utils.license b/license/jszip-utils.license deleted file mode 100644 index 971fc9da8b..0000000000 --- a/license/jszip-utils.license +++ /dev/null @@ -1,228 +0,0 @@ -JSZipUtils is dual licensed. You may use it under the MIT license or the GPLv3 license. - -The MIT License -Copyright (c) 2014 Stuart Knightley, David Duponchel - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -GPL version 3 - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 -Copyright (C) 2007 Free Software Foundation, Inc. http://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - - Preamble -The GNU General Public License is a free, copyleft license for software and other kinds of works. - -The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. - -When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. - -Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. - -Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and modification follow. - - TERMS AND CONDITIONS -Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based on the Program. - -To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. - -Source Code. - -The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. - -A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. - -The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. - -The Corresponding Source for a work in source code form is that same work. - -Basic Permissions. - -All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. - -Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. - -When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. - -Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. - -Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is released under this License and any conditions added under section - -This requirement modifies the requirement in section 4 to "keep intact all notices". -c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. - -A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. - -Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: - -a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. - -d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. - -If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). - -The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. - -Additional Terms. - -"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or authors of the material; or - -e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. - -All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. - -Termination. - -You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). - -However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. - -Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. - -Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. - -Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. - -Patents. - -A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. - -If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. - -A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. - -No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - -Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. - -Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. - -Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. - -Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/license/jszip.license b/license/jszip.license deleted file mode 100644 index 4535347a45..0000000000 --- a/license/jszip.license +++ /dev/null @@ -1,226 +0,0 @@ -JSZip is dual licensed. You may use it under the MIT license or the GPLv3 license. - -The MIT License -Copyright (c) 2009-2016 Stuart Knightley, David Duponchel, Franz Buchinger, Antonio Afonso - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -GPL version 3 - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 -Copyright (C) 2007 Free Software Foundation, Inc. http://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - - Preamble -The GNU General Public License is a free, copyleft license for software and other kinds of works. - -The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. - -When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. - -To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. - -For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. - -Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. - -For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. - -Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. - -Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. - -The precise terms and conditions for copying, distribution and modification follow. - - TERMS AND CONDITIONS -Definitions. - -"This License" refers to version 3 of the GNU General Public License. - -"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. - -"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. - -To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. - -A "covered work" means either the unmodified Program or a work based on the Program. - -To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. - -To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. - -An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. - -Source Code. - -The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. - -A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. - -The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. - -The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. - -The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. - -The Corresponding Source for a work in source code form is that same work. - -Basic Permissions. - -All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. - -You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. - -Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. - -Protecting Users' Legal Rights From Anti-Circumvention Law. - -No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. - -When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. - -Conveying Verbatim Copies. - -You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. - -You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. - -Conveying Modified Source Versions. - -You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: - -a) The work must carry prominent notices stating that you modified it, and giving a relevant date. - -b) The work must carry prominent notices stating that it is released under this License and any conditions added under section - -This requirement modifies the requirement in section 4 to "keep intact all notices". -c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. - -d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. - -A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. - -Conveying Non-Source Forms. - -You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: - -a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. - -b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. - -c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. - -d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. - -e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. - -A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. - -A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. - -"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. - -If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). - -The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. - -Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. - -Additional Terms. - -"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. - -When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. - -Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: - -a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or - -b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or - -c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or - -d) Limiting the use for publicity purposes of names of licensors or authors of the material; or - -e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or - -f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. - -All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. - -If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. - -Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. - -Termination. - -You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). - -However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. - -Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. - -Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. - -Acceptance Not Required for Having Copies. - -You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. - -Automatic Licensing of Downstream Recipients. - -Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. - -An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. - -You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. - -Patents. - -A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". - -A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. - -Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. - -In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. - -If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. - -If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. - -A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. - -Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. - -No Surrender of Others' Freedom. - -If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. - -Use with the GNU Affero General Public License. - -Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. - -Revised Versions of this License. - -The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. - -If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. - -Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. - -Disclaimer of Warranty. - -THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -Limitation of Liability. - -IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -Interpretation of Sections 15 and 16. - -If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. \ No newline at end of file diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json new file mode 100644 index 0000000000..d4915a7e76 --- /dev/null +++ b/npm-shrinkwrap.json @@ -0,0 +1,6512 @@ +{ + "name": "builder", + "version": "1.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@babel/code-frame": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", + "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.18.6" + } + }, + "@babel/compat-data": { + "version": "7.21.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.7.tgz", + "integrity": "sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==", + "dev": true + }, + "@babel/core": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.8.tgz", + "integrity": "sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.21.4", + "@babel/generator": "^7.21.5", + "@babel/helper-compilation-targets": "^7.21.5", + "@babel/helper-module-transforms": "^7.21.5", + "@babel/helpers": "^7.21.5", + "@babel/parser": "^7.21.8", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.5.tgz", + "integrity": "sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.5.tgz", + "integrity": "sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.21.5", + "@babel/helper-validator-option": "^7.21.0", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "@babel/helper-environment-visitor": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.21.5.tgz", + "integrity": "sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==", + "dev": true + }, + "@babel/helper-module-imports": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", + "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.4" + } + }, + "@babel/helper-module-transforms": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.5.tgz", + "integrity": "sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.21.5", + "@babel/helper-module-imports": "^7.21.4", + "@babel/helper-simple-access": "^7.21.5", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz", + "integrity": "sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz", + "integrity": "sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==", + "dev": true, + "requires": { + "@babel/types": "^7.21.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "requires": { + "@babel/types": "^7.18.6" + } + }, + "@babel/helper-string-parser": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz", + "integrity": "sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true + }, + "@babel/helpers": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.5.tgz", + "integrity": "sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==", + "dev": true, + "requires": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.5", + "@babel/types": "^7.21.5" + } + }, + "@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.21.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.8.tgz", + "integrity": "sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.21.4.tgz", + "integrity": "sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.21.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.21.4.tgz", + "integrity": "sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.20.2" + } + }, + "@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + } + }, + "@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + } + }, + "@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dev": true, + "requires": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true + }, + "@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + } + }, + "@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.25.6" + } + }, + "@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + } + }, + "@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/types": { + "version": "7.21.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.5.tgz", + "integrity": "sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.21.5", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.5.0.tgz", + "integrity": "sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/core": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.5.0.tgz", + "integrity": "sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/reporters": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-resolve-dependencies": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "jest-watcher": "^29.5.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@jest/environment": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.5.0.tgz", + "integrity": "sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0" + } + }, + "@jest/expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==", + "dev": true, + "requires": { + "expect": "^29.5.0", + "jest-snapshot": "^29.5.0" + } + }, + "@jest/expect-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.5.0.tgz", + "integrity": "sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3" + } + }, + "@jest/fake-timers": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.5.0.tgz", + "integrity": "sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "@jest/globals": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.5.0.tgz", + "integrity": "sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/types": "^29.5.0", + "jest-mock": "^29.5.0" + } + }, + "@jest/reporters": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.5.0.tgz", + "integrity": "sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jest/schemas": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.4.3.tgz", + "integrity": "sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.25.16" + } + }, + "@jest/source-map": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.4.3.tgz", + "integrity": "sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.15", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.5.0.tgz", + "integrity": "sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.5.0.tgz", + "integrity": "sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.5.0.tgz", + "integrity": "sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.5.0", + "@jridgewell/trace-mapping": "^0.3.15", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "@jest/types": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.5.0.tgz", + "integrity": "sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@kessler/tableify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@kessler/tableify/-/tableify-1.0.2.tgz", + "integrity": "sha512-e4psVV9Fe2eBfS9xK2rzQ9lE5xS4tARm7EJzDb6sVZy3F+EMyHJ67i0NdBVR9BRyQx7YhogMCbB6R1QwXuBxMg==" + }, + "@sinclair/typebox": { + "version": "0.25.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", + "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", + "dev": true + }, + "@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==" + }, + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", + "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0" + } + }, + "@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "requires": { + "defer-to-connect": "^2.0.1" + } + }, + "@types/babel__core": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.0.tgz", + "integrity": "sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.18.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.5.tgz", + "integrity": "sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==" + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/node": { + "version": "18.16.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.3.tgz", + "integrity": "sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==", + "dev": true + }, + "@types/prettier": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.2.tgz", + "integrity": "sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==", + "dev": true + }, + "@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true + }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + }, + "dependencies": { + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + } + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==" + }, + "array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==" + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + }, + "arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + } + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "babel-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.5.0.tgz", + "integrity": "sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==", + "dev": true, + "requires": { + "@jest/transform": "^29.5.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "bower-config": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/bower-config/-/bower-config-1.4.3.tgz", + "integrity": "sha512-MVyyUk3d1S7d2cl6YISViwJBc2VXCkxF5AUFykvN0PQj5FsUiMNSgAYTso18oRFfyZ6XEtjrgg9MAaufHbOwNw==", + "requires": { + "graceful-fs": "^4.1.3", + "minimist": "^0.2.1", + "mout": "^1.0.0", + "osenv": "^0.1.3", + "untildify": "^2.1.0", + "wordwrap": "^0.0.3" + }, + "dependencies": { + "minimist": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.2.4.tgz", + "integrity": "sha512-Pkrrm8NjyQ8yVt8Am9M+yUt74zE3iokhzbG1bFVNjLB92vwM71hf40RkEsryg98BujhVOncKm/C1xROxZ030LQ==" + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==" + }, + "cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "requires": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + } + }, + "call-bind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", + "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "requires": { + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.1", + "set-function-length": "^1.1.1" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + } + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001482", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz", + "integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "check-dependencies": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/check-dependencies/-/check-dependencies-1.1.0.tgz", + "integrity": "sha512-GDrbGzzJ6Gc6tQh87HBMGhrJ4UWIlR9MKJwgvlrJyj/gWvTYYb2jQetKbajt/EYK5Y8/4g7gH2LEvq8GdUWTag==", + "requires": { + "bower-config": "^1.4.0", + "chalk": "^2.1.0", + "findup-sync": "^2.0.0", + "lodash.camelcase": "^4.3.0", + "minimist": "^1.2.0", + "semver": "^5.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "findup-sync": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", + "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^3.1.0", + "micromatch": "^3.0.4", + "resolve-dir": "^1.0.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz", + "integrity": "sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colors": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "integrity": "sha512-ENwblkFQpqqia6b++zLD/KUWafYlVY/UNnAp7oz7LY7E924wmpye416wBOmvv/HMWzl8gL1kJlfvId/1Dg176w==" + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "requires": { + "mimic-response": "^3.1.0" + }, + "dependencies": { + "mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" + } + } + }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" + }, + "define-data-property": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", + "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "requires": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "requires": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.384", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.384.tgz", + "integrity": "sha512-I97q0MmRAAqj53+a8vZsDkEXBZki+ehYAOPzwtQzALip52aEp2+BJqHFtTlsfjoqVZYwPpHC8wM6MbsSZQ/Eqw==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "eol": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/eol/-/eol-0.9.1.tgz", + "integrity": "sha512-Ds/TEoZjwggRoz/Q2O7SE3i4Jm66mqTDfmdHdq/7DKVk3bro9Q8h6WdXKdPqFLMoqxrDK5SVRzHVPOS6uuGtrg==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.22.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.3.tgz", + "integrity": "sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==", + "requires": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.5", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.2", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.12", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.13" + } + }, + "es-set-tostringtag": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", + "integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", + "requires": { + "get-intrinsic": "^1.2.2", + "has-tostringtag": "^1.0.0", + "hasown": "^2.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "eventemitter2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "integrity": "sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ==" + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==" + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "expect": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.5.0.tgz", + "integrity": "sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "file-sync-cmp": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz", + "integrity": "sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs=" + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "findup-sync": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.3.0.tgz", + "integrity": "sha512-z8Nrwhi6wzxNMIbxlrTzuUW6KWuKkogZ/7OdDVq+0+kxn77KUH1nipx8iU6suqkHqc4y6n7a9A8IpmxY/pTjWg==", + "requires": { + "glob": "~5.0.0" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "fined": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-1.2.0.tgz", + "integrity": "sha512-ZYDqPLGxDkDhDZBjZBb+oD1+j0rA4E0pXY50eplAAOPg2N/gUBSSk5IM1/QhPfyVo19lJ+CvXpqfvk+b2p/8Ng==", + "requires": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^2.0.3", + "object.defaults": "^1.1.0", + "object.pick": "^1.2.0", + "parse-filepath": "^1.0.1" + } + }, + "flagged-respawn": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz", + "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" + }, + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "requires": { + "for-in": "^1.0.1" + } + }, + "form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==" + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "requires": { + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + } + } + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==" + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + }, + "getobject": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/getobject/-/getobject-1.0.2.tgz", + "integrity": "sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==" + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "requires": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + } + }, + "global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", + "requires": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.9", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", + "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" + }, + "grunt": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/grunt/-/grunt-1.5.3.tgz", + "integrity": "sha512-mKwmo4X2d8/4c/BmcOETHek675uOqw0RuA/zy12jaspWqvTp4+ZeQF1W+OTpcbncnaBsfbQJ6l0l4j+Sn/GmaQ==", + "requires": { + "dateformat": "~3.0.3", + "eventemitter2": "~0.4.13", + "exit": "~0.1.2", + "findup-sync": "~0.3.0", + "glob": "~7.1.6", + "grunt-cli": "~1.4.3", + "grunt-known-options": "~2.0.0", + "grunt-legacy-log": "~3.0.0", + "grunt-legacy-util": "~2.0.1", + "iconv-lite": "~0.4.13", + "js-yaml": "~3.14.0", + "minimatch": "~3.0.4", + "mkdirp": "~1.0.4", + "nopt": "~3.0.6", + "rimraf": "~3.0.2" + }, + "dependencies": { + "grunt-cli": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.4.3.tgz", + "integrity": "sha512-9Dtx/AhVeB4LYzsViCjUQkd0Kw0McN2gYpdmGYKtE2a5Yt7v1Q+HYZVWhqXc/kGnxlMtqKDxSwotiGeFmkrCoQ==", + "requires": { + "grunt-known-options": "~2.0.0", + "interpret": "~1.1.0", + "liftup": "~3.0.1", + "nopt": "~4.0.1", + "v8flags": "~3.2.0" + }, + "dependencies": { + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, + "minimatch": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", + "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "grunt-banner": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/grunt-banner/-/grunt-banner-0.6.0.tgz", + "integrity": "sha1-P4eQIdEj+linuloLb7a+QStYhaw=", + "requires": { + "chalk": "^1.1.0" + } + }, + "grunt-check-dependencies": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-check-dependencies/-/grunt-check-dependencies-1.0.0.tgz", + "integrity": "sha1-UYiVh8V+gn3enN9pt1CuCy+IHFA=", + "requires": { + "check-dependencies": "^1.0.1", + "lodash.clonedeep": "^4.5.0" + } + }, + "grunt-contrib-clean": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-clean/-/grunt-contrib-clean-2.0.0.tgz", + "integrity": "sha512-g5ZD3ORk6gMa5ugZosLDQl3dZO7cI3R14U75hTM+dVLVxdMNJCPVmwf9OUt4v4eWgpKKWWoVK9DZc1amJp4nQw==", + "requires": { + "async": "^2.6.1", + "rimraf": "^2.6.2" + }, + "dependencies": { + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "requires": { + "lodash": "^4.17.14" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "grunt-contrib-copy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/grunt-contrib-copy/-/grunt-contrib-copy-1.0.0.tgz", + "integrity": "sha1-cGDGWB6QS4qw0A8HbgqPbj58NXM=", + "requires": { + "chalk": "^1.1.1", + "file-sync-cmp": "^0.1.0" + } + }, + "grunt-known-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/grunt-known-options/-/grunt-known-options-2.0.0.tgz", + "integrity": "sha512-GD7cTz0I4SAede1/+pAbmJRG44zFLPipVtdL9o3vqx9IEyb7b4/Y3s7r6ofI3CchR5GvYJ+8buCSioDv5dQLiA==" + }, + "grunt-legacy-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log/-/grunt-legacy-log-3.0.0.tgz", + "integrity": "sha512-GHZQzZmhyq0u3hr7aHW4qUH0xDzwp2YXldLPZTCjlOeGscAOWWPftZG3XioW8MasGp+OBRIu39LFx14SLjXRcA==", + "requires": { + "colors": "~1.1.2", + "grunt-legacy-log-utils": "~2.1.0", + "hooker": "~0.2.3", + "lodash": "~4.17.19" + } + }, + "grunt-legacy-log-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/grunt-legacy-log-utils/-/grunt-legacy-log-utils-2.1.0.tgz", + "integrity": "sha512-lwquaPXJtKQk0rUM1IQAop5noEpwFqOXasVoedLeNzaibf/OPWjKYvvdqnEHNmU+0T0CaReAXIbGo747ZD+Aaw==", + "requires": { + "chalk": "~4.1.0", + "lodash": "~4.17.19" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "grunt-legacy-util": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/grunt-legacy-util/-/grunt-legacy-util-2.0.1.tgz", + "integrity": "sha512-2bQiD4fzXqX8rhNdXkAywCadeqiPiay0oQny77wA2F3WF4grPJXCvAcyoWUJV+po/b15glGkxuSiQCK299UC2w==", + "requires": { + "async": "~3.2.0", + "exit": "~0.1.2", + "getobject": "~1.0.0", + "hooker": "~0.2.3", + "lodash": "~4.17.21", + "underscore.string": "~3.3.5", + "which": "~2.0.2" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "grunt-mkdir": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grunt-mkdir/-/grunt-mkdir-1.1.0.tgz", + "integrity": "sha512-FRE17OYVveNbVJFX8GPGa5bzH2ZiAdBx3q0Kwk2Dg6l+TzLGaTdufUxiUWUbS2MERFacnmXZwDDOR5ZbYW0o+Q==" + }, + "grunt-stripcomments": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/grunt-stripcomments/-/grunt-stripcomments-0.7.2.tgz", + "integrity": "sha1-c4t3Z2Nnu/EmiJrpEJFRsB9a7w4=", + "requires": { + "chalk": "^1.1.3" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-property-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", + "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "requires": { + "get-intrinsic": "^1.2.2" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" + } + } + }, + "homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "requires": { + "parse-passwd": "^1.0.0" + } + }, + "hooker": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hooker/-/hooker-0.2.3.tgz", + "integrity": "sha512-t+UerCsQviSymAInD01Pw+Dn/usmz1sRO+3Zk1+lx8eg+WKpD2ulcwWqHHL0+aseRBr+3+vIhiG1K1JTwaIcTA==" + }, + "hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + }, + "http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "requires": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + } + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "internal-slot": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.6.tgz", + "integrity": "sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==", + "requires": { + "get-intrinsic": "^1.2.2", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + } + }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha512-CLM8SNMDu7C5psFCn6Wg/tgpj/bKAg7hc2gWqcuR9OD5Ft9PhBpIu8PLicPeis+xDd6YX2ncI8MCA64I9tftIA==" + }, + "is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "requires": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-core-module": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", + "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", + "requires": { + "has": "^1.0.3" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + } + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "requires": { + "isobject": "^3.0.1" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "requires": { + "is-unc-path": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "requires": { + "which-typed-array": "^1.1.11" + } + }, + "is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "requires": { + "unc-path-regex": "^0.1.2" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + }, + "istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.5.0.tgz", + "integrity": "sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/types": "^29.5.0", + "import-local": "^3.0.2", + "jest-cli": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "jest-cli": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.5.0.tgz", + "integrity": "sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==", + "dev": true, + "requires": { + "@jest/core": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + } + } + }, + "jest-circus": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.5.0.tgz", + "integrity": "sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/expect": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.5.0", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.5.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-config": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.5.0.tgz", + "integrity": "sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.5.0", + "@jest/types": "^29.5.0", + "babel-jest": "^29.5.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.5.0", + "jest-environment-node": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-runner": "^29.5.0", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-diff": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.5.0.tgz", + "integrity": "sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.5.0.tgz", + "integrity": "sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.5.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-environment-node": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.5.0.tgz", + "integrity": "sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-mock": "^29.5.0", + "jest-util": "^29.5.0" + } + }, + "jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true + }, + "jest-haste-map": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.5.0.tgz", + "integrity": "sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.5.0", + "jest-worker": "^29.5.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "dependencies": { + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-leak-detector": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.5.0.tgz", + "integrity": "sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==", + "dev": true, + "requires": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + } + }, + "jest-matcher-utils": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.5.0.tgz", + "integrity": "sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-message-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.5.0.tgz", + "integrity": "sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.5.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "jest-mock": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.5.0.tgz", + "integrity": "sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "jest-util": "^29.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true + }, + "jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true + }, + "jest-resolve": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.5.0.tgz", + "integrity": "sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.5.0", + "jest-validate": "^29.5.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-resolve-dependencies": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.5.0.tgz", + "integrity": "sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==", + "dev": true, + "requires": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.5.0" + } + }, + "jest-runner": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.5.0.tgz", + "integrity": "sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==", + "dev": true, + "requires": { + "@jest/console": "^29.5.0", + "@jest/environment": "^29.5.0", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.5.0", + "jest-haste-map": "^29.5.0", + "jest-leak-detector": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-resolve": "^29.5.0", + "jest-runtime": "^29.5.0", + "jest-util": "^29.5.0", + "jest-watcher": "^29.5.0", + "jest-worker": "^29.5.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-runtime": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.5.0.tgz", + "integrity": "sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==", + "dev": true, + "requires": { + "@jest/environment": "^29.5.0", + "@jest/fake-timers": "^29.5.0", + "@jest/globals": "^29.5.0", + "@jest/source-map": "^29.4.3", + "@jest/test-result": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-mock": "^29.5.0", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.5.0", + "jest-snapshot": "^29.5.0", + "jest-util": "^29.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-snapshot": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.5.0.tgz", + "integrity": "sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/traverse": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.5.0", + "@jest/transform": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/babel__traverse": "^7.0.6", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.5.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.5.0", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.5.0", + "jest-message-util": "^29.5.0", + "jest-util": "^29.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.5.0", + "semver": "^7.3.5" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.5.0.tgz", + "integrity": "sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-validate": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.5.0.tgz", + "integrity": "sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==", + "dev": true, + "requires": { + "@jest/types": "^29.5.0", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.5.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-watcher": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.5.0.tgz", + "integrity": "sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==", + "dev": true, + "requires": { + "@jest/test-result": "^29.5.0", + "@jest/types": "^29.5.0", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.5.0", + "string-length": "^4.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-worker": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.5.0.tgz", + "integrity": "sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.5.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "requires": { + "json-buffer": "3.0.1" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "license-downloader": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/license-downloader/-/license-downloader-1.0.8.tgz", + "integrity": "sha512-r+ui953Zy6nYgGvMiSPhscq1xZZNqxnEn9ZA6fXcKAPp69DmyDLmI74l6GWG7FwX5Ilk86P1d0WWJ/8/6K2nZA==", + "requires": { + "debug": "^4.3.4", + "got": "^12.6.0", + "jsonfile": "^6.1.0", + "rc": "^1.2.8" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "got": { + "version": "12.6.1", + "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", + "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", + "requires": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "license-report": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/license-report/-/license-report-6.5.0.tgz", + "integrity": "sha512-e8VbNeBb2UumBaTCciINTmW0MquM9HmSSGskCxFqIPjsypYHWlwoz5k6ydGP1lk5GaYUHBZsN+XoENJ5C9c04A==", + "requires": { + "@kessler/tableify": "^1.0.2", + "debug": "^4.3.4", + "eol": "^0.9.1", + "got": "^13.0.0", + "rc": "^1.2.8", + "semver": "^7.5.4", + "tablemark": "^3.0.0", + "text-table": "^0.2.0", + "visit-values": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } + }, + "liftup": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/liftup/-/liftup-3.0.1.tgz", + "integrity": "sha512-yRHaiQDizWSzoXk3APcA71eOI/UuhEkNN9DiW2Tt44mhYzX4joFoCZlxsSOF7RyeLlfqzFLQI1ngFq3ggMPhOw==", + "requires": { + "extend": "^3.0.2", + "findup-sync": "^4.0.0", + "fined": "^1.2.0", + "flagged-respawn": "^1.0.1", + "is-plain-object": "^2.0.4", + "object.map": "^1.0.1", + "rechoir": "^0.7.0", + "resolve": "^1.19.0" + }, + "dependencies": { + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "requires": { + "fill-range": "^7.1.1" + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "findup-sync": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-4.0.0.tgz", + "integrity": "sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ==", + "requires": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.0", + "micromatch": "^4.0.2", + "resolve-dir": "^1.0.1" + } + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } + } + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "requires": { + "tslib": "^2.0.3" + } + }, + "lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + } + } + }, + "make-iterator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", + "requires": { + "kind-of": "^6.0.2" + } + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "requires": { + "object-visit": "^1.0.0" + } + }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==" + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", + "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "mout": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/mout/-/mout-1.2.4.tgz", + "integrity": "sha512-mZb9uOruMWgn/fw28DG4/yE3Kehfk1zKCLhuDU2O3vlKdnBBr4XaOCqVTflJ5aODavGUPqFHZgrFX3NJVuxGhQ==" + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "nopt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", + "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "requires": { + "abbrev": "1" + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.0.tgz", + "integrity": "sha512-uVFpKhj5MheNBJRTiMZ9pE/7hD1QTeEvugSJW/OmLzAp78PB5O6adfMNTvmfKhXBkvCzC+rqifWcVYpGFwTjnw==" + }, + "npm-run-all": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/npm-run-all/-/npm-run-all-4.1.5.tgz", + "integrity": "sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==", + "requires": { + "ansi-styles": "^3.2.1", + "chalk": "^2.4.1", + "cross-spawn": "^6.0.5", + "memorystream": "^0.3.1", + "minimatch": "^3.0.4", + "pidtree": "^0.3.0", + "read-pkg": "^3.0.0", + "shell-quote": "^1.6.1", + "string.prototype.padend": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "requires": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "object.map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.map/-/object.map-1.0.1.tgz", + "integrity": "sha512-3+mAJu2PLfnSVGHwIWubpOFLscJANBKuB/6A4CxBstc4aqwQY0FWcsppuy4jU5GSB95yES5JHSI+33AWuS4k6w==", + "requires": { + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "requires": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=" + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "requires": { + "path-root-regex": "^0.1.0" + } + }, + "path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "requires": { + "pify": "^3.0.0" + } + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pidtree": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.3.1.tgz", + "integrity": "sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==" + }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + }, + "pretty-format": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.5.0.tgz", + "integrity": "sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.4.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true + }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + } + } + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "rechoir": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz", + "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==", + "requires": { + "resolve": "^1.9.0" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "regexp.prototype.flags": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "set-function-name": "^2.0.0" + } + }, + "repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", + "requires": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "requires": { + "lowercase-keys": "^3.0.0" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" + } + } + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "requires": { + "ret": "~0.1.10" + } + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" + }, + "sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "set-function-length": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", + "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "requires": { + "define-data-property": "^1.1.1", + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + } + }, + "set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "requires": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + } + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" + }, + "spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "split-text-to-chunks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/split-text-to-chunks/-/split-text-to-chunks-1.0.0.tgz", + "integrity": "sha512-HLtEwXK/T4l7QZSJ/kOSsZC0o5e2Xg3GzKKFxm0ZexJXw0Bo4CaEl39l7MCSRHk9EOOL5jT8JIDjmhTtcoe6lQ==", + "requires": { + "get-stdin": "^5.0.1", + "minimist": "^1.2.0" + } + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==" + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + } + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "string.prototype.padend": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.1.5.tgz", + "integrity": "sha512-DOB27b/2UTTD+4myKUFh+/fXWcu/UDyASIXfg+7VzoCNNGOfWvoyU/x5pvVHr++ztyt/oSYI1BcWBBG/hmlNjA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trim": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimend": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "string.prototype.trimstart": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, + "tablemark": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tablemark/-/tablemark-3.0.0.tgz", + "integrity": "sha512-7N05gRK7t6B4g8AtedUsKoKtPjplmUjOPr/V4kVB+7U3yGiB3WvKqMTTQzVCZyhfZUXgQFp9YyN9ZgC52uCPKw==", + "requires": { + "sentence-case": "^3.0.4", + "split-text-to-chunks": "^1.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + } + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==" + }, + "underscore.string": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.6.tgz", + "integrity": "sha512-VoC83HWXmCrF6rgkyxS9GHv8W9Q5nhMKho+OadDJGzL2oDYbYEppBaCMH6pFlwLeqj2QS+hhkw2kpXkSdD1JxQ==", + "requires": { + "sprintf-js": "^1.1.1", + "util-deprecate": "^1.0.2" + } + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "untildify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", + "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", + "requires": { + "os-homedir": "^1.0.0" + } + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "requires": { + "tslib": "^2.0.3" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + } + }, + "v8flags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", + "requires": { + "homedir-polyfill": "^1.0.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "visit-values": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/visit-values/-/visit-values-2.0.0.tgz", + "integrity": "sha512-vLFU70y3D915d611GnHYeHkEmq6ZZETzTH4P1hM6I9E3lBwH2VeBBEESe/bGCY+gAyK0qqLFn5bNFpui/GKmww==" + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.13.tgz", + "integrity": "sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.4", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "wordwrap": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 82a67fd4f3..00506422dd 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,45 @@ { "name": "builder", - "version": "0.0.0", - "homepage": "http://www.teamlab.com", + "version": "1.0.1", + "homepage": "https://www.onlyoffice.com", "private": true, - "build": { - "src": [ - "./**/sources/*.js", - "./Common/package.json", - "./DocService/package.json", - "./DocService/public/healthcheck.docx", - "./FileConverter/package.json", - "./FileConverter/bin/DoctRenderer.config", - "./Metrics/package.json", - "./SpellChecker/package.json", - "./Common/config/*.json", - "./Common/config/log4js/*.json", - "./Metrics/config/config.js" - ], - "dest": "./build/server" + "grunt": { + "copy": { + "server": { + "expand": true, + "src": [ + "./**/sources/*.js", + "./Common/package.json", + "./DocService/package.json", + "./DocService/public/healthcheck.docx", + "./FileConverter/package.json", + "./FileConverter/bin/DoctRenderer.config", + "./Metrics/package.json", + "./Common/config/*.json", + "./Common/config/log4js/*.json", + "./Metrics/config/config.js" + ], + "dest": "./build/server" + } + }, + "develop-copy": { + "server": {} + }, + "clean": { + "options": { + "force": true + }, + "server": "./build/server" + }, + "mkdir": { + "server": { + "options": { + "create": [ + "./build/server" + ] + } + } + } }, "postprocess": { "src": [ @@ -29,16 +51,49 @@ "./build/server/Common", "./build/server/DocService", "./build/server/FileConverter", - "./build/server/Metrics", - "./build/server/SpellChecker" + "./build/server/Metrics" ], "dependencies": { - "grunt": "^0.4.5", - "grunt-banner": "^0.6.0", - "grunt-check-dependencies": "^0.12.0", - "grunt-contrib-clean": "^1.0.0", - "grunt-contrib-copy": "^1.0.0", - "grunt-stripcomments": "^0.5.0", - "grunt-mkdir": "^1.0.0" + "grunt": "1.5.3", + "grunt-banner": "0.6.0", + "grunt-check-dependencies": "1.0.0", + "grunt-contrib-clean": "2.0.0", + "grunt-contrib-copy": "1.0.0", + "grunt-mkdir": "1.1.0", + "grunt-stripcomments": "0.7.2", + "license-downloader": "1.0.8", + "license-report": "6.5.0", + "npm-run-all": "4.1.5" + }, + "devDependencies": { + "@jest/globals": "29.5.0", + "cross-env": "7.0.3", + "jest": "29.5.0" + }, + "scripts": { + "perf-expired": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/checkFileExpire.js", + "perf-exif": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/fixImageExifRotation.js", + "perf-png": "cd ./DocService&& cross-env NODE_ENV=development-windows NODE_CONFIG_DIR=../Common/config node ../tests/perf/convertImageToPng.js", + "unit tests": "cd ./DocService && jest unit --inject-globals=false --config=../tests/jest.config.js", + "integration tests with server instance": "cd ./DocService && jest integration/withServerInstance --inject-globals=false --config=../tests/jest.config.js", + "integration database tests": "cd ./DocService && jest integration/databaseTests --inject-globals=false --config=../tests/jest.config.js", + "tests": "cd ./DocService && jest --inject-globals=false --config=../tests/jest.config.js", + "install:Common": "npm ci --prefix ./Common", + "install:DocService": "npm ci --prefix ./DocService", + "install:FileConverter": "npm ci --prefix ./FileConverter", + "install:Metrics": "npm ci --prefix ./Metrics", + "3d-party-lic-json:Common": "license-report --output=json --package=./Common/package.json --config ./3d-party-lic-report/license-report-config.json > ./3d-party-lic-report/license-report.json", + "3d-party-lic-json:DocService": "license-report --output=json --package=./DocService/package.json --config ./3d-party-lic-report/license-report-config.json > ./3d-party-lic-report/license-report.json", + "3d-party-lic-json:FileConverter": "license-report --output=json --package=./FileConverter/package.json --config ./3d-party-lic-report/license-report-config.json > ./3d-party-lic-report/license-report.json", + "3d-party-lic-json:Metrics": "license-report --output=json --package=./Metrics/package.json --config ./3d-party-lic-report/license-report-config.json > ./3d-party-lic-report/license-report.json", + "3d-party-lic-downloader": "license-downloader --source ./3d-party-lic-report/license-report.json", + "3d-party-lic-md-header": "node ./3d-party-lic-report/json2md.js ./3DPARTY.md", + "3d-party-lic-md": "node ./3d-party-lic-report/json2md.js ./3DPARTY.md ./3d-party-lic-report/license-report.ext.json", + "3d-party-lic-report:Common": "run-s 3d-party-lic-json:Common 3d-party-lic-downloader 3d-party-lic-md", + "3d-party-lic-report:DocService": "run-s 3d-party-lic-json:DocService 3d-party-lic-downloader 3d-party-lic-md", + "3d-party-lic-report:FileConverter": "run-s 3d-party-lic-json:FileConverter 3d-party-lic-downloader 3d-party-lic-md", + "3d-party-lic-report:Metrics": "run-s 3d-party-lic-json:Metrics 3d-party-lic-downloader 3d-party-lic-md", + "3d-party-lic-report": "run-s 3d-party-lic-md-header 3d-party-lic-report:*", + "build": "run-p install:*" } } diff --git a/run-mac.command b/run-mac.command deleted file mode 100755 index 48db55ad52..0000000000 --- a/run-mac.command +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash - -echo "----------------------------------------" -echo "Copy file to converter" -echo "----------------------------------------" - -BASEDIR="$(cd "$(dirname "$0")" && pwd)" - -echo "$BASEDIR" - -CreateDir() { - if [ ! -d $1 ]; then - mkdir -pv $1; - fi -} - -NpmInstall() { - cd $1 - echo "Module path: $(pwd)" - npm install -} - -RunCommand() { - TAB_NAME=$1 - COMMAND=$2 - osascript \ - -e "tell application \"Terminal\"" \ - -e "tell application \"System Events\" to keystroke \"t\" using {command down}" \ - -e "do script \"printf '\\\e]1;$TAB_NAME\\\a'; $COMMAND\" in front window" \ - -e "end tell" > /dev/null -} - -CreateDir "$BASEDIR/App_Data" -CreateDir "$BASEDIR/FileConverter/bin" -CreateDir "$BASEDIR/FileConverter/bin/HtmlFileInternal" - -cd "$BASEDIR/FileConverter/bin" - -cp -v "../../../core/build/bin/mac_64/icudtl_dat.S" "." -cp -v "../../../core/build/bin/mac_64/x2t" "." -cp -v "../../../core/build/bin/icu/mac_64/libicudata.55.1.dylib" "." -cp -v "../../../core/build/bin/icu/mac_64/libicuuc.55.1.dylib" "." -cp -v "../../../core/build/lib/mac_64/libDjVuFile.dylib" "." -cp -v "../../../core/build/lib/mac_64/libHtmlFile.dylib" "." -cp -v "../../../core/build/lib/mac_64/libHtmlRenderer.dylib" "." -cp -v "../../../core/build/lib/mac_64/libPdfReader.dylib" "." -cp -v "../../../core/build/lib/mac_64/libPdfWriter.dylib" "." -cp -v "../../../core/build/lib/mac_64/libUnicodeConverter.dylib" "." -cp -v "../../../core/build/lib/mac_64/libXpsFile.dylib" "." -cp -v "../../../core/build/lib/mac_64/libascdocumentscore.dylib" "." -cp -v "../../../core/build/lib/mac_64/libdoctrenderer.dylib" "." - -ln -sifv libicuuc.55.1.dylib libicuuc.55.dylib -ln -sifv libicudata.55.1.dylib libicudata.55.dylib -chmod -v +x x2t - -SEARCH='..\/..\/OfficeWeb' -REPLACE='..\/..\/..\/sdkjs' -sed "s/$SEARCH/$REPLACE/g" "../../../core/build/lib/DoctRenderer.config" > "DoctRenderer.config" - -echo $BASEDIR -chmod -v +x $BASEDIR/../core/build/bin/AllFontsGen/mac_64 -bash -cv "$BASEDIR/../core/build/bin/AllFontsGen/mac_64 '' '$BASEDIR/../sdkjs/Common/AllFonts.js' '$BASEDIR/../sdkjs/Common/Images' '$BASEDIR/FileConverter/bin/font_selection.bin'" - - -echo "----------------------------------------" -echo "Install node.js modules " -echo "----------------------------------------" - -NpmInstall "$BASEDIR/DocService" -NpmInstall "$BASEDIR/Common" -NpmInstall "$BASEDIR/FileConverter" -NpmInstall "$BASEDIR/SpellChecker" - - - -echo "----------------------------------------" -echo "Run services" -echo "----------------------------------------" - -mysql.server restart -RunCommand "RabbitMQ Server" "/usr/local/sbin/rabbitmq-server" -RunCommand "Redis" "redis-server /usr/local/etc/redis.conf" - -RunCommand "Server" "export NODE_CONFIG_DIR=$BASEDIR/Common/config && export NODE_ENV=development-mac && cd $BASEDIR/DocService/sources && node server.js" -RunCommand "GC" "export NODE_CONFIG_DIR=$BASEDIR/Common/config && export NODE_ENV=development-mac && cd $BASEDIR/DocService/sources && node gc.js" -RunCommand "Converter" "export NODE_CONFIG_DIR=$BASEDIR/Common/config && export NODE_ENV=development-mac && export DYLD_LIBRARY_PATH=../../FileConverter/bin/ && cd $BASEDIR/FileConverter/sources && node convertermaster.js" - - diff --git a/run.bat b/run.bat deleted file mode 100644 index 5150dd694b..0000000000 --- a/run.bat +++ /dev/null @@ -1,80 +0,0 @@ -ECHO OFF - -reg Query "HKLM\Hardware\Description\System\CentralProcessor\0" | find /i "x86" > NUL && set OS=32||set OS=64 - -ECHO. -ECHO ---------------------------------------- -ECHO copy file to converter -ECHO ---------------------------------------- - -mkdir "%~dp0\App_Data" -mkdir "%~dp0\FileConverter\bin" -mkdir "%~dp0\FileConverter\bin\HtmlFileInternal" - -cd /D "%~dp0\FileConverter\bin" || goto ERROR -copy "..\..\..\core\build\bin\win_64\icudt.dll" "." -copy "..\..\..\core\build\bin\icu\win_%OS%\icudt55.dll" "." -copy "..\..\..\core\build\bin\icu\win_%OS%\icuuc55.dll" "." -copy "..\..\..\core\build\lib\DoctRenderer.config" "." -copy "..\..\..\core\build\lib\win_%OS%\doctrenderer.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\HtmlRenderer.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\DjVuFile.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\XpsFile.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\PdfReader.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\PdfWriter.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\HtmlFile.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\UnicodeConverter.dll" "." -copy "..\..\..\core\build\lib\win_%OS%\HtmlFileInternal.exe" ".\HtmlFileInternal" -xcopy /s/h/e/k/c/y/q "..\..\..\core\build\cef\win_%OS%" ".\HtmlFileInternal" -copy "..\..\..\core\build\bin\win_%OS%\x2t.exe" "." - -"..\..\..\core\build\bin\AllFontsGen\win_%OS%.exe" "%windir%\Fonts" "%~dp0\..\sdkjs\common\AllFonts.js" "%~dp0\..\sdkjs\common\Images" "%~dp0\FileConverter\bin\font_selection.bin" - -mkdir "%~dp0\SpellChecker\dictionaries" -cd /D "%~dp0\SpellChecker" || goto ERROR -xcopy /s/e/k/c/y/q "..\..\dictionaries" ".\dictionaries" - -ECHO. -ECHO ---------------------------------------- -ECHO Start build skd-all.js -ECHO ---------------------------------------- -CD /D %~dp0\..\sdkjs\build -call npm install -g grunt-cli -call npm install -call grunt --src="./configs" --level=WHITESPACE_ONLY --formatting=PRETTY_PRINT - - -ECHO. -ECHO ---------------------------------------- -ECHO Install node.js modules -ECHO ---------------------------------------- - -CD /D %~dp0\DocService || goto ERROR -call npm install - -cd /D ..\Common || goto ERROR -call npm install - -cd /D ..\FileConverter || goto ERROR -call npm install - -cd /D ..\SpellChecker || goto ERROR -call npm install - -SET RUN_DIR=%~dp0 -SET NODE_ENV=development-windows -SET NODE_CONFIG_DIR=%RUN_DIR%\Common\config - -cd "%RUN_DIR%\DocService\sources" -start /min /b node server.js -start /min /b node gc.js - -cd "%RUN_DIR%\FileConverter\sources" -start /min /b node convertermaster.js - -cd "%RUN_DIR%\SpellChecker\sources" -start /min /b node server.js - -:ERROR -:SUCCESS -pause diff --git a/schema/dameng/createdb.sql b/schema/dameng/createdb.sql new file mode 100644 index 0000000000..329e98e5ae --- /dev/null +++ b/schema/dameng/createdb.sql @@ -0,0 +1,41 @@ +-- +-- Create schema onlyoffice +-- + +-- CREATE DATABASE onlyoffice ENCODING = 'UTF8' CONNECTION LIMIT = -1; + +-- ---------------------------- +-- Table structure for doc_changes +-- ---------------------------- +CREATE TABLE doc_changes +( +tenant varchar(255) NOT NULL, +id varchar(255) NOT NULL, +change_id int NOT NULL, +user_id varchar(255) NOT NULL, +user_id_original varchar(255) NOT NULL, +user_name varchar(255) NOT NULL, +change_data text NOT NULL, +change_date TIMESTAMP(6) NOT NULL, +PRIMARY KEY (tenant, id, change_id) +); + +-- ---------------------------- +-- Table structure for task_result +-- ---------------------------- +CREATE TABLE task_result +( +tenant varchar(255) NOT NULL, +id varchar(255) NOT NULL, +status int NOT NULL, +status_info int NOT NULL, +created_at TIMESTAMP(6) DEFAULT NOW(), +last_open_date TIMESTAMP(6) NOT NULL, +user_index int NOT NULL DEFAULT 1, +change_id int NOT NULL DEFAULT 0, +callback text NOT NULL, +baseurl text NOT NULL, +password text NULL, +additional text NULL, +PRIMARY KEY (tenant, id) +); diff --git a/schema/dameng/removetbl.sql b/schema/dameng/removetbl.sql new file mode 100644 index 0000000000..a00877fa8e --- /dev/null +++ b/schema/dameng/removetbl.sql @@ -0,0 +1,2 @@ +DROP TABLE IF EXISTS "doc_changes"; +DROP TABLE IF EXISTS "task_result"; \ No newline at end of file diff --git a/schema/json-api/opening-file.json b/schema/json-api/opening-file.json new file mode 100644 index 0000000000..93516e652b --- /dev/null +++ b/schema/json-api/opening-file.json @@ -0,0 +1,86 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://example.com/example.json", + "type": "object", + "required": [ + "document", + "editorConfig" + ], + "properties": { + "document": { + "$id": "#/properties/document", + "type": "object", + "required": [ + "key", + "permissions", + "url" + ], + "properties": { + "key": { + "$id": "#/properties/document/properties/key", + "type": "string" + }, + "permissions": { + "$id": "#/properties/document/properties/permissions", + "type": "object", + "required": [], + "additionalProperties": true + }, + "url": { + "$id": "#/properties/document/properties/url", + "type": "string" + } + }, + "additionalProperties": true + }, + "editorConfig": { + "$id": "#/properties/editorConfig", + "type": "object", + "required": [ + "callbackUrl", + "mode" + ], + "properties": { + "callbackUrl": { + "$id": "#/properties/editorConfig/properties/callbackUrl", + "type": "string" + }, + "mode": { + "$id": "#/properties/editorConfig/properties/mode", + "type": "string" + }, + "user": { + "$id": "#/properties/editorConfig/properties/user", + "type": "object", + "required": [], + "properties": { + "group": { + "$id": "#/properties/editorConfig/properties/user/properties/group", + "type": "string" + }, + "id": { + "$id": "#/properties/editorConfig/properties/user/properties/id", + "anyOf": [ + { + "$id": "#/properties/editorConfig/properties/user/properties/id/anyOf/0", + "type": "string" + }, + { + "$id": "#/properties/editorConfig/properties/user/properties/id/anyOf/1", + "type": "integer" + } + ] + }, + "name": { + "$id": "#/properties/editorConfig/properties/user/properties/name", + "type": "string" + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true + } + }, + "additionalProperties": true +} \ No newline at end of file diff --git a/schema/mssql/createdb.sql b/schema/mssql/createdb.sql new file mode 100644 index 0000000000..a89a5a9d1a --- /dev/null +++ b/schema/mssql/createdb.sql @@ -0,0 +1,35 @@ +-- CREATE DATABASE onlyoffice; +-- GO + +-- USE onlyoffice; + +CREATE TABLE doc_changes( + tenant NVARCHAR(255) NOT NULL, + id NVARCHAR(255) NOT NULL, + change_id DECIMAL NOT NULL CONSTRAINT unsigned_doc_changes CHECK(change_id BETWEEN 0 AND 4294967295), + user_id NVARCHAR(255) NOT NULL, + user_id_original NVARCHAR(255) NOT NULL, + user_name NVARCHAR(255) NOT NULL, + change_data NVARCHAR(MAX) NOT NULL, + change_date DATETIME NOT NULL, + UNIQUE (tenant, id, change_id) +); + +CREATE TABLE task_result ( + tenant NVARCHAR(255) NOT NULL, + id NVARCHAR(255) NOT NULL, + status SMALLINT NOT NULL, + status_info INT NOT NULL, + created_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL, + last_open_date DATETIME NOT NULL, + user_index DECIMAL DEFAULT 1 NOT NULL, + change_id DECIMAL DEFAULT 0 NOT NULL, + callback NVARCHAR(MAX) NOT NULL, + baseurl NVARCHAR(MAX) NOT NULL, + password NVARCHAR(MAX) NULL, + additional NVARCHAR(MAX) NULL, + UNIQUE (tenant, id), + CONSTRAINT unsigned_task_result CHECK(change_id BETWEEN 0 AND 4294967295 AND user_index BETWEEN 0 AND 4294967295) +); + +GO diff --git a/schema/mssql/removetbl.sql b/schema/mssql/removetbl.sql new file mode 100644 index 0000000000..a0fc5dbb80 --- /dev/null +++ b/schema/mssql/removetbl.sql @@ -0,0 +1,2 @@ +-- USE onlyoffice; +DROP TABLE IF EXISTS doc_changes, task_result; \ No newline at end of file diff --git a/schema/mysql/createdb.sql b/schema/mysql/createdb.sql index 267e94e38c..04f69a2195 100644 --- a/schema/mysql/createdb.sql +++ b/schema/mysql/createdb.sql @@ -18,14 +18,15 @@ -- Create schema onlyoffice -- -CREATE DATABASE IF NOT EXISTS onlyoffice DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; -USE onlyoffice; +-- CREATE DATABASE IF NOT EXISTS onlyoffice DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; +-- USE onlyoffice; -- -- Definition of table `doc_changes` -- CREATE TABLE IF NOT EXISTS `doc_changes` ( + `tenant` varchar(255) NOT NULL, `id` varchar(255) NOT NULL, `change_id` int(10) unsigned NOT NULL, `user_id` varchar(255) NOT NULL, @@ -33,7 +34,7 @@ CREATE TABLE IF NOT EXISTS `doc_changes` ( `user_name` varchar(255) NOT NULL, `change_data` longtext NOT NULL, `change_date` datetime NOT NULL, - PRIMARY KEY (`id`,`change_id`) + PRIMARY KEY (`tenant`, `id`,`change_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- @@ -43,20 +44,25 @@ CREATE TABLE IF NOT EXISTS `doc_changes` ( /*!40000 ALTER TABLE `doc_changes` DISABLE KEYS */; /*!40000 ALTER TABLE `doc_changes` ENABLE KEYS */; + -- -- Definition of table `task_result` -- CREATE TABLE IF NOT EXISTS `task_result` ( + `tenant` varchar(255) NOT NULL, `id` varchar(255) NOT NULL, `status` tinyint(3) NOT NULL, `status_info` int(10) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `last_open_date` datetime NOT NULL, `user_index` int(10) unsigned NOT NULL DEFAULT 1, `change_id` int(10) unsigned NOT NULL DEFAULT 0, - `callback` text NOT NULL, + `callback` longtext NOT NULL, `baseurl` text NOT NULL, - PRIMARY KEY (`id`) + `password` longtext NULL, + `additional` longtext NULL, + PRIMARY KEY (`tenant`, `id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- diff --git a/schema/mysql/removedb.sql b/schema/mysql/removedb.sql deleted file mode 100644 index b2aa9b4d3b..0000000000 --- a/schema/mysql/removedb.sql +++ /dev/null @@ -1 +0,0 @@ -DROP DATABASE IF EXISTS onlyoffice; \ No newline at end of file diff --git a/schema/mysql/removetbl.sql b/schema/mysql/removetbl.sql index 849d59a9ea..cab8b7e537 100644 --- a/schema/mysql/removetbl.sql +++ b/schema/mysql/removetbl.sql @@ -1,7 +1,7 @@ -USE onlyoffice; +-- USE onlyoffice; -- -- Drop tables -- DROP TABLE IF EXISTS `doc_callbacks`; DROP TABLE IF EXISTS `doc_changes`; -DROP TABLE IF EXISTS `task_result`; \ No newline at end of file +DROP TABLE IF EXISTS `task_result`; diff --git a/schema/mysql/upgrade/upgradev630.sql b/schema/mysql/upgrade/upgradev630.sql new file mode 100644 index 0000000000..d5bdc3a4e6 --- /dev/null +++ b/schema/mysql/upgrade/upgradev630.sql @@ -0,0 +1,21 @@ +DELIMITER DLM00 + +DROP PROCEDURE IF EXISTS upgrade630 DLM00 + +CREATE PROCEDURE upgrade630() +BEGIN + + IF (SELECT DATA_TYPE FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'task_result' AND COLUMN_NAME = 'callback') <> 'longtext' THEN + ALTER TABLE `task_result` CHANGE COLUMN `callback` `callback` LONGTEXT NOT NULL ; + END IF; + + IF NOT EXISTS(SELECT * FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'task_result' AND COLUMN_NAME = 'created_at') THEN + ALTER TABLE `task_result` ADD COLUMN `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP AFTER `status_info`; + ALTER TABLE `task_result` ADD COLUMN `password` LONGTEXT NULL AFTER `baseurl`; + END IF; + +END DLM00 + +CALL upgrade630() DLM00 + +DELIMITER ; diff --git a/schema/mysql/upgrade/upgradev710.sql b/schema/mysql/upgrade/upgradev710.sql new file mode 100644 index 0000000000..fdbdf52a62 --- /dev/null +++ b/schema/mysql/upgrade/upgradev710.sql @@ -0,0 +1,19 @@ +DELIMITER DLM00 + +DROP PROCEDURE IF EXISTS upgrade710 DLM00 + +CREATE PROCEDURE upgrade710() +BEGIN + + IF NOT EXISTS(SELECT * FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'task_result' AND COLUMN_NAME = 'additional') THEN + ALTER TABLE `task_result` ADD COLUMN `additional` LONGTEXT NULL DEFAULT NULL AFTER `password`; + END IF; + +END DLM00 + +CALL upgrade710() DLM00 + +DELIMITER ; + + + diff --git a/schema/mysql/upgrade/upgradev720.sql b/schema/mysql/upgrade/upgradev720.sql new file mode 100644 index 0000000000..51e483c262 --- /dev/null +++ b/schema/mysql/upgrade/upgradev720.sql @@ -0,0 +1,32 @@ +DELIMITER DLM00 + +DROP PROCEDURE IF EXISTS upgrade720 DLM00 + +CREATE PROCEDURE upgrade720() +BEGIN + + IF NOT EXISTS(SELECT * FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'task_result' AND COLUMN_NAME = 'tenant') THEN + SET SQL_SAFE_UPDATES=0; + ALTER TABLE `task_result` ADD COLUMN `tenant` VARCHAR(255) NULL FIRST; + UPDATE `task_result` SET `tenant`='localhost' WHERE `tenant` IS NULL; + ALTER TABLE `task_result` CHANGE COLUMN `tenant` `tenant` VARCHAR(255) NOT NULL; + ALTER TABLE `task_result` DROP PRIMARY KEY; + ALTER TABLE `task_result` ADD PRIMARY KEY (`tenant`, `id`); + SET SQL_SAFE_UPDATES=1; + END IF; + + IF NOT EXISTS(SELECT * FROM information_schema.`COLUMNS` WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'doc_changes' AND COLUMN_NAME = 'tenant') THEN + SET SQL_SAFE_UPDATES=0; + ALTER TABLE `doc_changes` ADD COLUMN `tenant` VARCHAR(255) NULL FIRST; + UPDATE `doc_changes` SET `tenant`='localhost' WHERE `tenant` IS NULL; + ALTER TABLE `doc_changes` CHANGE COLUMN `tenant` `tenant` VARCHAR(255) NOT NULL; + ALTER TABLE `doc_changes` DROP PRIMARY KEY; + ALTER TABLE `doc_changes` ADD PRIMARY KEY (`tenant`, `id`,`change_id`); + SET SQL_SAFE_UPDATES=1; + END IF; + +END DLM00 + +CALL upgrade720() DLM00 + +DELIMITER ; diff --git a/schema/oracle/createdb.sql b/schema/oracle/createdb.sql new file mode 100644 index 0000000000..b6e799d9aa --- /dev/null +++ b/schema/oracle/createdb.sql @@ -0,0 +1,42 @@ +-- You must be logged in as SYS(sysdba) user. +-- Here, "onlyoffice" is a PBD(service) name. +alter session set container = xepdb1; + +-- Oracle uses users as namespaces for tables creation. In "onlyoffice.table_name" onlyoffice is a user name, so table_name exist only at this namespace. +-- ---------------------------- +-- Table structure for doc_changes +-- ---------------------------- + +CREATE TABLE doc_changes ( + tenant NVARCHAR2(255) NOT NULL, + id NVARCHAR2(255) NOT NULL, + change_id NUMBER NOT NULL, + user_id NVARCHAR2(255) NOT NULL, + user_id_original NVARCHAR2(255) NOT NULL, + user_name NVARCHAR2(255) NOT NULL, + change_data NCLOB NOT NULL, + change_date TIMESTAMP NOT NULL, + CONSTRAINT doc_changes_unique UNIQUE (tenant, id, change_id), + CONSTRAINT doc_changes_unsigned_int CHECK (change_id between 0 and 4294967295) +); + +-- ---------------------------- +-- Table structure for task_result +-- ---------------------------- + +CREATE TABLE task_result ( + tenant NVARCHAR2(255) NOT NULL, + id NVARCHAR2(255) NOT NULL, + status NUMBER NOT NULL, + status_info NUMBER NOT NULL, + created_at TIMESTAMP DEFAULT SYSDATE NOT NULL, + last_open_date TIMESTAMP NOT NULL, + user_index NUMBER DEFAULT 1 NOT NULL, + change_id NUMBER DEFAULT 0 NOT NULL, + callback NCLOB, -- codebase uses '' as default values here, but Oracle treat '' as NULL, so NULL permitted for this value. + baseurl NCLOB, -- codebase uses '' as default values here, but Oracle treat '' as NULL, so NULL permitted for this value. + password NCLOB NULL, + additional NCLOB NULL, + CONSTRAINT task_result_unique UNIQUE (tenant, id), + CONSTRAINT task_result_unsigned_int CHECK (user_index BETWEEN 0 AND 4294967295 AND change_id BETWEEN 0 AND 4294967295) +); diff --git a/schema/oracle/removetbl.sql b/schema/oracle/removetbl.sql new file mode 100644 index 0000000000..ba79fbde1d --- /dev/null +++ b/schema/oracle/removetbl.sql @@ -0,0 +1,6 @@ +-- You must be logged in as SYS(sysdba) user. +-- Here, "onlyoffice" is a PBD(service) name. +alter session set container = xepdb1; + +DROP TABLE onlyoffice.doc_changes CASCADE CONSTRAINTS PURGE; +DROP TABLE onlyoffice.task_result CASCADE CONSTRAINTS PURGE; \ No newline at end of file diff --git a/schema/postgresql/createdb.sql b/schema/postgresql/createdb.sql index 7433b51815..0d0545230a 100644 --- a/schema/postgresql/createdb.sql +++ b/schema/postgresql/createdb.sql @@ -8,6 +8,7 @@ -- Table structure for doc_changes -- ---------------------------- CREATE TABLE IF NOT EXISTS "public"."doc_changes" ( +"tenant" varchar(255) COLLATE "default" NOT NULL, "id" varchar(255) COLLATE "default" NOT NULL, "change_id" int4 NOT NULL, "user_id" varchar(255) COLLATE "default" NOT NULL, @@ -15,7 +16,7 @@ CREATE TABLE IF NOT EXISTS "public"."doc_changes" ( "user_name" varchar(255) COLLATE "default" NOT NULL, "change_data" text COLLATE "default" NOT NULL, "change_date" timestamp without time zone NOT NULL, -PRIMARY KEY ("id", "change_id") +PRIMARY KEY ("tenant", "id", "change_id") ) WITH (OIDS=FALSE); @@ -23,19 +24,23 @@ WITH (OIDS=FALSE); -- Table structure for task_result -- ---------------------------- CREATE TABLE IF NOT EXISTS "public"."task_result" ( +"tenant" varchar(255) COLLATE "default" NOT NULL, "id" varchar(255) COLLATE "default" NOT NULL, "status" int2 NOT NULL, "status_info" int4 NOT NULL, +"created_at" timestamp without time zone DEFAULT NOW(), "last_open_date" timestamp without time zone NOT NULL, "user_index" int4 NOT NULL DEFAULT 1, "change_id" int4 NOT NULL DEFAULT 0, "callback" text COLLATE "default" NOT NULL, "baseurl" text COLLATE "default" NOT NULL, -PRIMARY KEY ("id") +"password" text COLLATE "default" NULL, +"additional" text COLLATE "default" NULL, +PRIMARY KEY ("tenant", "id") ) WITH (OIDS=FALSE); -CREATE OR REPLACE FUNCTION merge_db(_id varchar(255), _status int2, _status_info int4, _last_open_date timestamp without time zone, _user_index int4, _change_id int4, _callback text, _baseurl text, OUT isupdate char(5), OUT userindex int4) AS +CREATE OR REPLACE FUNCTION merge_db(_tenant varchar(255), _id varchar(255), _status int2, _status_info int4, _last_open_date timestamp without time zone, _user_index int4, _change_id int4, _callback text, _baseurl text, OUT isupdate char(5), OUT userindex int4) AS $$ DECLARE t_var "public"."task_result"."user_index"%TYPE; @@ -43,7 +48,11 @@ BEGIN LOOP -- first try to update the key -- note that "a" must be unique - UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1 WHERE id = _id RETURNING user_index into userindex; + IF ((_callback <> '') IS TRUE) AND ((_baseurl <> '') IS TRUE) THEN + UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1,callback=_callback,baseurl=_baseurl WHERE tenant = _tenant AND id = _id RETURNING user_index into userindex; + ELSE + UPDATE "public"."task_result" SET last_open_date=_last_open_date, user_index=user_index+1 WHERE tenant = _tenant AND id = _id RETURNING user_index into userindex; + END IF; IF found THEN isupdate := 'true'; RETURN; @@ -52,7 +61,7 @@ BEGIN -- if someone else inserts the same key concurrently, -- we could get a unique-key failure BEGIN - INSERT INTO "public"."task_result"(id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) VALUES(_id, _status, _status_info, _last_open_date, _user_index, _change_id, _callback, _baseurl) RETURNING user_index into userindex; + INSERT INTO "public"."task_result"(tenant, id, status, status_info, last_open_date, user_index, change_id, callback, baseurl) VALUES(_tenant, _id, _status, _status_info, _last_open_date, _user_index, _change_id, _callback, _baseurl) RETURNING user_index into userindex; isupdate := 'false'; RETURN; EXCEPTION WHEN unique_violation THEN diff --git a/schema/postgresql/removedb.sql b/schema/postgresql/removedb.sql deleted file mode 100644 index b2aa9b4d3b..0000000000 --- a/schema/postgresql/removedb.sql +++ /dev/null @@ -1 +0,0 @@ -DROP DATABASE IF EXISTS onlyoffice; \ No newline at end of file diff --git a/schema/postgresql/upgrade/upgradev630.sql b/schema/postgresql/upgrade/upgradev630.sql new file mode 100644 index 0000000000..57a8ea13b5 --- /dev/null +++ b/schema/postgresql/upgrade/upgradev630.sql @@ -0,0 +1,15 @@ +DO $$ + BEGIN + BEGIN + ALTER TABLE "task_result" ADD COLUMN "created_at" timestamp without time zone DEFAULT NOW(); + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'column created_at already exists.'; + END; + + BEGIN + ALTER TABLE "task_result" ADD COLUMN "password" text; + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'column password already exists.'; + END; + END; +$$ \ No newline at end of file diff --git a/schema/postgresql/upgrade/upgradev710.sql b/schema/postgresql/upgrade/upgradev710.sql new file mode 100644 index 0000000000..249b4a7ce2 --- /dev/null +++ b/schema/postgresql/upgrade/upgradev710.sql @@ -0,0 +1,9 @@ +DO $$ + BEGIN + BEGIN + ALTER TABLE "task_result" ADD COLUMN "additional" text; + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'column additional already exists.'; + END; + END; +$$ \ No newline at end of file diff --git a/schema/postgresql/upgrade/upgradev720.sql b/schema/postgresql/upgrade/upgradev720.sql new file mode 100644 index 0000000000..0979958089 --- /dev/null +++ b/schema/postgresql/upgrade/upgradev720.sql @@ -0,0 +1,21 @@ +DO $$ + BEGIN + BEGIN + ALTER TABLE "task_result" ADD COLUMN "tenant" varchar(255) COLLATE "default" NOT NULL DEFAULT 'localhost'; + ALTER TABLE "task_result" ALTER COLUMN "tenant" DROP DEFAULT; + ALTER TABLE "task_result" DROP CONSTRAINT IF EXISTS task_result_pkey; + ALTER TABLE "task_result" ADD PRIMARY KEY ("tenant", "id"); + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'column `tenant` already exists.'; + END; + + BEGIN + ALTER TABLE "doc_changes" ADD COLUMN "tenant" varchar(255) COLLATE "default" NOT NULL DEFAULT 'localhost'; + ALTER TABLE "doc_changes" ALTER COLUMN "tenant" DROP DEFAULT; + ALTER TABLE "doc_changes" DROP CONSTRAINT IF EXISTS doc_changes_pkey; + ALTER TABLE "doc_changes" ADD PRIMARY KEY ("tenant", "id", "change_id"); + EXCEPTION + WHEN duplicate_column THEN RAISE NOTICE 'column `tenant` already exists.'; + END; + END; +$$ diff --git a/tests/env-setup.js b/tests/env-setup.js new file mode 100644 index 0000000000..1dee40d207 --- /dev/null +++ b/tests/env-setup.js @@ -0,0 +1,47 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const platforms = { + 'win32': 'windows', + 'darwin': 'mac', + 'linux': 'linux' +}; +const platform = platforms[process.platform]; + +process.env.NODE_ENV = `development-${platform}`; +process.env.NODE_CONFIG_DIR = '../Common/config'; + +if (platform === 'mac') { + process.env.DYLD_LIBRARY_PATH = '../FileConverter/bin/'; +} else if (platform === 'linux') { + process.env.LD_LIBRARY_PATH = '../FileConverter/bin/'; +} diff --git a/tests/integration/databaseTests/baseConnector.tests.js b/tests/integration/databaseTests/baseConnector.tests.js new file mode 100644 index 0000000000..cc4022a06a --- /dev/null +++ b/tests/integration/databaseTests/baseConnector.tests.js @@ -0,0 +1,475 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const { describe, test, expect, afterAll } = require('@jest/globals'); +const config = require('../../../Common/node_modules/config'); + +const baseConnector = require('../../../DocService/sources/databaseConnectors/baseConnector'); +const operationContext = require('../../../Common/sources/operationContext'); +const taskResult = require('../../../DocService/sources/taskresult'); +const commonDefines = require('../../../Common/sources/commondefines'); +const constants = require('../../../Common/sources/constants'); +const utils = require("../../../Common/sources/utils"); +const configSql = config.get('services.CoAuthoring.sql'); + +const ctx = new operationContext.Context(); +const cfgDbType = configSql.get('type'); +const cfgTableResult = configSql.get('tableResult'); +const cfgTableChanges = configSql.get('tableChanges'); +const dbTypes = { + oracle: { + number: 'NUMBER', + string: 'NVARCHAR(50)' + }, + mssql: { + number: 'INT', + string: 'NVARCHAR(50)' + }, + mysql: { + number: 'INT', + string: 'VARCHAR(50)' + }, + dameng: { + number: 'INT', + string: 'VARCHAR(50)' + }, + postgres: { + number: 'INT', + string: 'VARCHAR(50)' + }, + number: function () { + return this[cfgDbType].number; + }, + string: function () { + return this[cfgDbType].string; + } +} + +const insertCases = { + 5: 'baseConnector-insert()-tester-5-rows', + 500: 'baseConnector-insert()-tester-500-rows', + 1000: 'baseConnector-insert()-tester-1000-rows', + 5000: 'baseConnector-insert()-tester-5000-rows', + 10000: 'baseConnector-insert()-tester-10000-rows' +}; +const changesCases = { + range: 'baseConnector-getChangesPromise()-tester', + index: 'baseConnector-getChangesIndexPromise()-tester', + delete: 'baseConnector-deleteChangesPromise()-tester' +}; +const emptyCallbacksCase = [ + 'baseConnector-getEmptyCallbacks()-tester-0', + 'baseConnector-getEmptyCallbacks()-tester-1', + 'baseConnector-getEmptyCallbacks()-tester-2', + 'baseConnector-getEmptyCallbacks()-tester-3', + 'baseConnector-getEmptyCallbacks()-tester-4', +]; +const documentsWithChangesCase = [ + 'baseConnector-getDocumentsWithChanges()-tester-0', + 'baseConnector-getDocumentsWithChanges()-tester-1' +]; +const getExpiredCase = [ + 'baseConnector-getExpired()-tester-0', + 'baseConnector-getExpired()-tester-1', + 'baseConnector-getExpired()-tester-2', +]; +const getCountWithStatusCase = [ + 'baseConnector-getCountWithStatusCase()-tester-0' +]; +const upsertCases = { + insert: 'baseConnector-upsert()-tester-row-inserted', + update: 'baseConnector-upsert()-tester-row-updated' +}; + +function createChanges(changesLength, date) { + const objChanges = [ + { + docid: '__ffff_127.0.0.1new.docx41692082262909', + change: '"64;AgAAADEA//8BAG+X6xGnEAMAjgAAAAIAAAAEAAAABAAAAAUAAACCAAAAggAAAA4AAAAwAC4AMAAuADAALgAwAA=="', + time: date, + user: 'uid-18', + useridoriginal: 'uid-1' + } + ]; + + const length = changesLength - 1; + for (let i = 1; i <= length; i++) { + objChanges.push( + { + docid: '__ffff_127.0.0.1new.docx41692082262909', + change: '"39;CgAAADcAXwA2ADQAMAACABwAAQAAAAAAAAABAAAALgAAAAAAAAAA"', + time: date, + user: 'uid-18', + useridoriginal: 'uid-1' + } + ); + } + + return objChanges; +} + +async function getRowsCountById(table, id) { + const result = await executeSql(`SELECT COUNT(id) AS count FROM ${table} WHERE id = '${id}';`); + // Return type of COUNT() in postgres is bigint which treats as string by connector. Dameng DB returns js bigint type. + return Number(result[0].count); +} + +async function noRowsExistenceCheck(table, id) { + const noRows = await getRowsCountById(table, id); + expect(noRows).toEqual(0); +} + +function deleteRowsByIds(table, ids) { + const idToDelete = ids.map(id => `id = '${id}'`).join(' OR '); + return executeSql(`DELETE FROM ${table} WHERE ${idToDelete};`); +} + +function executeSql(sql, values = []) { + return new Promise((resolve, reject) => { + baseConnector.sqlQuery(ctx, sql, function (error, result) { + if (error) { + reject(error) + } else { + resolve(result) + } + }, false, false, values); + }); +} + +function createTask(id, callback = '', baseurl = '') { + const task = new taskResult.TaskResultData(); + task.tenant = ctx.tenant; + task.key = id; + task.status = commonDefines.FileStatus.None; + task.statusInfo = constants.NO_ERROR; + task.callback = callback; + task.baseurl = baseurl; + task.completeDefaults(); + + return task; +} + +function insertIntoResultTable(dateNow, task) { + let cbInsert = task.callback; + if (task.callback) { + const userCallback = new baseConnector.UserCallback(); + userCallback.fromValues(task.userIndex, task.callback); + cbInsert = userCallback.toSQLInsert(); + } + + const columns = ['tenant', 'id', 'status', 'status_info', 'last_open_date', 'user_index', 'change_id', 'callback', 'baseurl']; + const values = []; + const placeholder = [ + baseConnector.addSqlParameter(task.tenant, values), + baseConnector.addSqlParameter(task.key, values), + baseConnector.addSqlParameter(task.status, values), + baseConnector.addSqlParameter(task.statusInfo, values), + baseConnector.addSqlParameter(dateNow, values), + baseConnector.addSqlParameter(task.userIndex, values), + baseConnector.addSqlParameter(task.changeId, values), + baseConnector.addSqlParameter(cbInsert, values), + baseConnector.addSqlParameter(task.baseurl, values) + ]; + + return executeSql(`INSERT INTO ${cfgTableResult}(${columns.join(', ')}) VALUES(${placeholder.join(', ')});`, values); +} + +afterAll(async function () { + const insertIds = Object.values(insertCases); + const changesIds = Object.values(changesCases); + const upsertIds = Object.values(upsertCases); + + const tableChangesIds = [...emptyCallbacksCase, ...documentsWithChangesCase, ...changesIds, ...insertIds]; + const tableResultIds = [...emptyCallbacksCase, ...documentsWithChangesCase, ...getExpiredCase, ...getCountWithStatusCase, ...upsertIds]; + + const deletionPool = [ + deleteRowsByIds(cfgTableChanges, tableChangesIds), + deleteRowsByIds(cfgTableResult, tableResultIds), + executeSql('DROP TABLE test_table;') + ]; + + await Promise.allSettled(deletionPool); + baseConnector.closePool?.(); +}); + +// Assumed that at least default DB was installed and configured. +describe('Base database connector', function () { + test('Availability of configured DB', async function () { + const result = await baseConnector.healthCheck(ctx); + + expect(result.length).toEqual(1); + }); + + test('Correct return format of requested rows', async function() { + const result = await baseConnector.healthCheck(ctx); + + // The [[constructor]] field is referring to a parent class instance, so for Object-like values it is equal to itself. + expect(result.constructor).toEqual(Array); + // SQL in healthCheck() request column with value 1, so we expect only one value. The default format, that used here is [{ columnName: columnValue }, { columnName: columnValue }]. + expect(result.length).toEqual(1); + expect(result[0].constructor).toEqual(Object); + expect(Object.values(result[0]).length).toEqual(1); + // Value itself. + expect(Object.values(result[0])[0]).toEqual(1); + }); + + test('Correct return format of changing in DB', async function () { + const createTableSql = `CREATE TABLE test_table(num ${dbTypes.number()});` + const alterTableSql = `INSERT INTO test_table VALUES(1);`; + + await executeSql(createTableSql); + const result = await executeSql(alterTableSql); + + expect(result).toEqual({ affectedRows: 1 }); + }); + + describe('DB tables existence', function () { + const tables = { + [cfgTableResult]: constants.TABLE_RESULT_SCHEMA.map(column => { return { column_name: column } }), + [cfgTableChanges]: constants.TABLE_CHANGES_SCHEMA.map(column => { return { column_name: column } }) + }; + + for (const table in tables) { + test(`${table} table existence`, async function () { + const result = await baseConnector.getTableColumns(ctx, table); + for (const row of tables[table]) { + expect(result).toContainEqual(row); + } + }); + } + + const table = "unused_table"; + test(`${table} table absence`, async function () { + const result = await baseConnector.getTableColumns(ctx, table); + expect(result).toEqual([]); + }); + }); + + describe('Changes manipulations', function () { + const date = new Date(); + const index = 0; + const user = { + id: 'uid-18', + idOriginal: 'uid-1', + username: 'John Smith', + indexUser: 8, + view: false + }; + + describe('Add changes', function () { + for (const testCase in insertCases) { + test(`${testCase} rows inserted`, async function () { + const docId = insertCases[testCase]; + const objChanges = createChanges(+testCase, date); + + await noRowsExistenceCheck(cfgTableChanges, docId); + + await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user); + const result = await getRowsCountById(cfgTableChanges, docId); + + expect(result).toEqual(objChanges.length); + }); + } + }); + + describe('Get and delete changes', function () { + const changesCount = 10; + const objChanges = createChanges(changesCount, date); + + test('Get changes in range', async function () { + const docId = changesCases.range; + const additionalChangesCount = 5; + const dayBefore = new Date(); + dayBefore.setDate(dayBefore.getDate() - 1); + const limitedByDateChanges = createChanges(additionalChangesCount, dayBefore); + const fullChanges = [...objChanges, ...limitedByDateChanges]; + + await noRowsExistenceCheck(cfgTableChanges, docId); + + await baseConnector.insertChangesPromise(ctx, fullChanges, docId, index, user); + + const result = await baseConnector.getChangesPromise(ctx, docId, index, changesCount); + expect(result.length).toEqual(changesCount); + + dayBefore.setSeconds(dayBefore.getSeconds() + 1); + const resultByDate = await baseConnector.getChangesPromise(ctx, docId, index, changesCount + additionalChangesCount, dayBefore); + expect(resultByDate.length).toEqual(additionalChangesCount); + }); + + test('Get changes index', async function () { + const docId = changesCases.index; + + await noRowsExistenceCheck(cfgTableChanges, docId); + + await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user); + + const result = await baseConnector.getChangesIndexPromise(ctx, docId); + + // We created 10 changes rows, change_id: 0..9, changes index is MAX(change_id). + const expected = [{ change_id: 9 }]; + expect(result).toEqual(expected); + }); + + test('Delete changes', async function () { + const docId = changesCases.delete; + + await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user); + + // Deleting 6 rows. + await baseConnector.deleteChangesPromise(ctx, docId, 4); + + const result = await getRowsCountById(cfgTableChanges, docId); + + // Rest rows. + expect(result).toEqual(4); + }); + }); + + test('Get empty callbacks' , async function () { + const idCount = 5; + const notNullCallbacks = idCount - 2; + + const resultBefore = await baseConnector.getEmptyCallbacks(ctx); + + // Adding non-empty callbacks. + for (let i = 0; i < notNullCallbacks; i++) { + const task = createTask(emptyCallbacksCase[i], 'some_callback'); + await insertIntoResultTable(date, task); + } + + // Adding empty callbacks. + for (let i = notNullCallbacks; i < idCount; i++) { + const task = createTask(emptyCallbacksCase[i], ''); + await insertIntoResultTable(date, task); + } + + // Adding same amount of changes with same tenant and id. + const objChanges = createChanges(1, date); + for (let i = 0; i < idCount; i++) { + await baseConnector.insertChangesPromise(ctx, objChanges, emptyCallbacksCase[i], index, user); + } + + const resultAfter = await baseConnector.getEmptyCallbacks(ctx); + + expect(resultAfter.length).toEqual(resultBefore.length + idCount - notNullCallbacks); + }); + + test('Get documents with changes', async function () { + const objChanges = createChanges(1, date); + + const resultBeforeNewRows = await baseConnector.getDocumentsWithChanges(ctx); + + for (const id of documentsWithChangesCase) { + const task = createTask(id); + await Promise.all([ + baseConnector.insertChangesPromise(ctx, objChanges, id, index, user), + insertIntoResultTable(date, task) + ]); + } + + const resultAfterNewRows = await baseConnector.getDocumentsWithChanges(ctx); + expect(resultAfterNewRows.length).toEqual(resultBeforeNewRows.length + documentsWithChangesCase.length); + }); + + test('Get expired', async function () { + const maxCount = 100; + const dayBefore = new Date(); + dayBefore.setDate(dayBefore.getDate() - 1); + + const resultBeforeNewRows = await baseConnector.getExpired(ctx, maxCount, 0); + + for (const id of getExpiredCase) { + const task = createTask(id); + await insertIntoResultTable(dayBefore, task); + } + + // 3 rows were added. + const resultAfterNewRows = await baseConnector.getExpired(ctx, maxCount + 3, 0); + + expect(resultAfterNewRows.length).toEqual(resultBeforeNewRows.length + getExpiredCase.length); + }); + + test('Get Count With Status', async function () { + let countWithStatus; + let unknownStatus = 99;//to avoid collision with running server + let EXEC_TIMEOUT = 30000 + utils.getConvertionTimeout(undefined); + countWithStatus = await baseConnector.getCountWithStatus(ctx, unknownStatus, EXEC_TIMEOUT); + expect(countWithStatus).toEqual(0); + for (const id of getCountWithStatusCase) { + const task = createTask(id); + task.status = unknownStatus; + await insertIntoResultTable(date, task); + } + countWithStatus = await baseConnector.getCountWithStatus(ctx, unknownStatus, EXEC_TIMEOUT); + expect(countWithStatus).toEqual(getCountWithStatusCase.length); + }); + }); + + describe('upsert() method', function () { + test('New row inserted', async function () { + const task = createTask(upsertCases.insert); + + await noRowsExistenceCheck(cfgTableResult, task.key); + + const result = await baseConnector.upsert(ctx, task); + + // isInsert should be true because of insert operation, insertId should be 1 by default. + const expected = { isInsert: true, insertId: 1 }; + expect(result).toEqual(expected); + + const insertedResult = await getRowsCountById(cfgTableResult, task.key); + + expect(insertedResult).toEqual(1); + }); + + test('Row updated', async function () { + const task = createTask(upsertCases.update, '', 'some-url'); + + await noRowsExistenceCheck(cfgTableResult, task.key); + + await baseConnector.upsert(ctx, task); + + // Changing baseurl to verify upsert() changing the row. + task.baseurl = 'some-updated-url'; + const result = await baseConnector.upsert(ctx, task); + + // isInsert should be false because of update operation, insertId should be 2 by updating clause. + const expected = { isInsert: false, insertId: 2 }; + expect(result).toEqual(expected); + + const updatedRow = await executeSql(`SELECT id, baseurl FROM ${cfgTableResult} WHERE id = '${task.key}';`); + + const expectedUrlChanges = [{ id: task.key, baseurl: 'some-updated-url' }]; + expect(updatedRow).toEqual(expectedUrlChanges); + }); + }); +}); diff --git a/tests/integration/withServerInstance/forgottenFilesCommnads.tests.js b/tests/integration/withServerInstance/forgottenFilesCommnads.tests.js new file mode 100644 index 0000000000..076a12d1f7 --- /dev/null +++ b/tests/integration/withServerInstance/forgottenFilesCommnads.tests.js @@ -0,0 +1,294 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const { describe, test, expect, afterAll, beforeAll } = require('@jest/globals'); +const http = require('http'); + +const { signToken } = require('../../../DocService/sources/DocsCoServer'); +const storage = require('../../../Common/sources/storage-base'); +const constants = require('../../../Common/sources/commondefines'); +const operationContext = require('../../../Common/sources/operationContext'); +const utils = require("../../../Common/sources/utils"); + +const config = require('../../../Common/node_modules/config'); + +const cfgForgottenFiles = config.get('services.CoAuthoring.server.forgottenfiles'); +const cfgForgottenFilesName = config.get('services.CoAuthoring.server.forgottenfilesname'); +const cfgTokenAlgorithm = config.get('services.CoAuthoring.token.session.algorithm'); +const cfgSecretOutbox = config.get('services.CoAuthoring.secret.outbox'); +const cfgTokenOutboxExpires = config.get('services.CoAuthoring.token.outbox.expires'); +const cfgTokenEnableRequestOutbox = config.get('services.CoAuthoring.token.enable.request.outbox'); +const cfgStorageName = config.get('storage.name'); +const cfgEndpoint = config.get('storage.endpoint'); +const cfgBucketName = config.get('storage.bucketName'); +const ctx = new operationContext.Context(); + +const testFilesNames = { + get: 'DocService-DocsCoServer-forgottenFilesCommands-getForgotten-integration-test', + delete1: 'DocService-DocsCoServer-forgottenFilesCommands-deleteForgotten-integration-test', + // delete2: 'DocService-DocsCoServer-forgottenFilesCommands-deleteForgotten-2-integration-test', + // delete3: 'DocService-DocsCoServer-forgottenFilesCommands-deleteForgotten-3-integration-test', + getList: 'DocService-DocsCoServer-forgottenFilesCommands-getForgottenList-integration-test' +}; + +function makeRequest(requestBody, timeout = 5000) { + return new Promise(async (resolve, reject) => { + const timer = setTimeout(() => reject('Request timeout'), timeout); + + let body = ''; + if (cfgTokenEnableRequestOutbox) { + const secret = utils.getSecretByElem(cfgSecretOutbox); + const token = await signToken(ctx, requestBody, cfgTokenAlgorithm, cfgTokenOutboxExpires, constants.c_oAscSecretType.Inbox, secret); + body = JSON.stringify({ token }); + } else { + body = JSON.stringify(requestBody); + } + + const options = { + port: '8000', + path: '/coauthoring/CommandService.ashx', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(body) + } + }; + const request = http.request(options, (response) => { + response.setEncoding('utf8'); + + let data = ''; + response.on('data', (chunk) => { + data += chunk + }); + response.on('end', () => { + resolve(data); + clearTimeout(timer); + }); + }); + + request.on('error', (error) => { + reject(error); + clearTimeout(timer); + }); + + request.write(body); + request.end(); + }); +} + +function getKeysDirectories(keys) { + return keys.map(value => value.split('/')[0]); +} + +beforeAll(async function () { + const buffer = Buffer.from('Forgotten commands test file'); + for (const index in testFilesNames) { + await storage.putObject(ctx, `${testFilesNames[index]}/${cfgForgottenFilesName}.docx`, buffer, buffer.length, cfgForgottenFiles); + } +}); + +afterAll(async function () { + const keys = await storage.listObjects(ctx, '', cfgForgottenFiles); + const keysDirectories = getKeysDirectories(keys); + const deletePromises = keysDirectories.filter(key => key.includes('DocService-DocsCoServer-forgottenFilesCommands')) + .map(filteredKey => storage.deletePath(ctx, filteredKey, cfgForgottenFiles)); + console.log(`keys:`+JSON.stringify(keys)); + console.log(`keysDirectories:`+JSON.stringify(keysDirectories)); + return Promise.allSettled(deletePromises); +}); + +// Assumed, that server is already up. +describe('Command service', function () { + describe('Forgotten files commands parameters validation', function () { + describe('Invalid key format', function () { + const tests = ['getForgotten', 'deleteForgotten']; + const addSpecialCases = (invalidRequests, expected, testSubject) => { + invalidRequests.push({ + c: testSubject + }); + expected.push({ error: 1}); + + invalidRequests.push({ + c: testSubject, + key: null + }); + expected.push({ + key: null, + error: 1 + }); + }; + + for (const testSubject of tests) { + test(testSubject, async function () { + const invalidKeys = [true, [], {}, 1, 1.1]; + const invalidRequests = invalidKeys.map(key => { + return { + c: testSubject, + key + } + }); + + const expected = invalidKeys.map(key => { + return { + key, + error: 1, + }; + }); + + addSpecialCases(invalidRequests, expected, testSubject); + + for (const index in invalidRequests) { + const actualResponse = await makeRequest(invalidRequests[index]); + const actual = JSON.parse(actualResponse); + + expect(actual).toEqual(expected[index]); + } + }); + } + }); + }); + + describe('Forgotten files commands verification', function () { + describe('getForgotten', function () { + const createExpected = ({ key, error }) => { + const validKey = typeof key === 'string' && error === 0 + let urlPattern; + if ("storage-fs" === cfgStorageName) { + urlPattern = 'http://localhost:8000/cache/files/forgotten/--key--/output.docx/output.docx'; + } else { + let host = cfgEndpoint.slice(0, "https://".length) + cfgBucketName + "." + cfgEndpoint.slice("https://".length); + if (host[host.length - 1] === '/') { + host = host.slice(0, -1); + } + urlPattern = host + '/files/forgotten/--key--/output.docx'; + } + + const expected = { key, error }; + + if (validKey) { + expected.url = urlPattern.replace('--key--', key); + } + + return expected; + }; + + const testCases = { + 'Single key': { key: testFilesNames.get, error: 0 }, + 'Not existed key': { key: '--not-existed--', error: 1 }, + }; + + for (const testCase in testCases) { + test(testCase, async () => { + const requestBody = { + c: 'getForgotten', + key: testCases[testCase].key + }; + + const actualResponse = await makeRequest(requestBody); + + const expected = createExpected(testCases[testCase]); + const actual = JSON.parse(actualResponse); + + if (actual.url) { + actual.url = actual.url.split('?')[0]; + } + + expect(actual).toEqual(expected); + }); + } + }); + + describe('deleteForgotten', function () { + const createExpected = ({ key, error }) => { + return { + key, + error + }; + }; + + const testCases = { + 'Single key': { key: testFilesNames.delete1, error: 0 }, + 'Not existed key': { key: '--not-existed--', error: 1 }, + }; + + for (const testCase in testCases) { + test(testCase, async () => { + const requestBody = { + c: 'deleteForgotten', + key: testCases[testCase].key + }; + + const alreadyExistedDirectories = getKeysDirectories(await storage.listObjects(ctx, '', cfgForgottenFiles)); + const directoryToBeDeleted = testCases[testCase].error !== 0 ? '--not-existed--' : testCases[testCase].key; + const shouldExist = alreadyExistedDirectories.filter(directory => directoryToBeDeleted !== directory); + + const actualResponse = await makeRequest(requestBody); + + const expected = createExpected(testCases[testCase]); + const actual = JSON.parse(actualResponse); + + const directoriesExistedAfterDeletion = getKeysDirectories(await storage.listObjects(ctx, '', cfgForgottenFiles)); + expect(actual).toEqual(expected); + // Checking that files not existing on disk/cloud. + expect(shouldExist).toEqual(directoriesExistedAfterDeletion); + }); + } + }); + + describe('getForgottenList', function () { + test('Main case', async () => { + const requestBody = { + c: 'getForgottenList' + }; + + const stateBeforeChanging = await makeRequest(requestBody); + const alreadyExistedDirectories = JSON.parse(stateBeforeChanging); + + const docId = 'DocService-DocsCoServer-forgottenFilesCommands-getForgottenList-2-integration-test'; + const buffer = Buffer.from('getForgottenList test file'); + await storage.putObject(ctx, `${docId}/${cfgForgottenFilesName}.docx`, buffer, buffer.length, cfgForgottenFiles); + alreadyExistedDirectories.keys.push(docId); + + const actualResponse = await makeRequest(requestBody); + const actual = JSON.parse(actualResponse); + const expected = { + error: 0, + keys: alreadyExistedDirectories.keys + } + + actual.keys?.sort(); + expected.keys.sort(); + expect(actual).toEqual(expected); + }); + }); + }); +}); \ No newline at end of file diff --git a/tests/integration/withServerInstance/storage.tests.js b/tests/integration/withServerInstance/storage.tests.js new file mode 100644 index 0000000000..dba681d0ec --- /dev/null +++ b/tests/integration/withServerInstance/storage.tests.js @@ -0,0 +1,323 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const {jest, describe, test, expect} = require('@jest/globals'); +const http = require('http'); +const https = require('https'); +const fs = require('fs'); +const { Readable } = require('stream'); + +let testFileData1 = "test1"; +let testFileData2 = "test22"; +let testFileData3 = "test333"; +let testFileData4 = testFileData3; + +jest.mock("fs/promises", () => ({ + ...jest.requireActual('fs/promises'), + cp: jest.fn().mockImplementation((from, to) => fs.writeFileSync(to, testFileData3)) +})); +const { cp } = require('fs/promises'); + +const operationContext = require('../../../Common/sources/operationContext'); +const tenantManager = require('../../../Common/sources/tenantManager'); +const storage = require('../../../Common/sources/storage-base'); +const utils = require('../../../Common/sources/utils'); +const commonDefines = require("../../../Common/sources/commondefines"); +const config = require('../../../Common/node_modules/config'); + +const cfgCacheStorage = config.get('storage'); +const cfgPersistentStorage = utils.deepMergeObjects({}, cfgCacheStorage, config.get('persistentStorage')); + +const ctx = operationContext.global; +const rand = Math.floor(Math.random() * 1000000); +const testDir = "DocService-DocsCoServer-storage-" + rand; +const baseUrl = "http://localhost:8000"; +const urlType = commonDefines.c_oAscUrlTypes.Session; +let testFile1 = testDir + "/test1.txt"; +let testFile2 = testDir + "/test2.txt"; +let testFile3 = testDir + "/test3.txt"; +let testFile4 = testDir + "/test4.txt"; +let specialDirCache = ""; +let specialDirForgotten = "forgotten"; + +console.debug(`testDir: ${testDir}`) + +function getStorageCfg(specialDir) { + return specialDir ? cfgPersistentStorage : cfgCacheStorage; +} + +function request(url) { + return new Promise(resolve => { + let module = url.startsWith('https') ? https : http; + module.get(url, response => { + let data = ''; + response.on('data', _data => (data += _data)); + response.on('end', () => resolve(data)); + }); + }); +} +function runTestForDir(ctx, isMultitenantMode, specialDir) { + let oldMultitenantMode = tenantManager.isMultitenantMode(); + test("start listObjects", async () => { + //todo set in all tests do not rely on test order + tenantManager.setMultitenantMode(isMultitenantMode); + + let list = await storage.listObjects(ctx, testDir, specialDir); + expect(list).toEqual([]); + }); + test("putObject", async () => { + let buffer = Buffer.from(testFileData1); + let res = await storage.putObject(ctx, testFile1, buffer, buffer.length, specialDir); + expect(res).toEqual(undefined); + let list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile1].sort()); + }); + test("putObject-stream", async () => { + let buffer = Buffer.from(testFileData2); + const stream = Readable.from(buffer); + let res = await storage.putObject(ctx, testFile2, stream, buffer.length, specialDir); + expect(res).toEqual(undefined); + let list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile1, testFile2].sort()); + }); + if ("storage-fs" === getStorageCfg(specialDir).name) { + test("UploadObject", async () => { + let res = await storage.uploadObject(ctx, testFile3, "createReadStream.txt", specialDir); + expect(res).toEqual(undefined); + expect(cp).toHaveBeenCalled(); + let list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile1, testFile2, testFile3].sort()); + }); + } else { + test("uploadObject", async () => { + const spy = jest.spyOn(fs, 'createReadStream').mockReturnValue(testFileData3); + let res = await storage.uploadObject(ctx, testFile3, "createReadStream.txt", specialDir); + expect(res).toEqual(undefined); + let list = await storage.listObjects(ctx, testDir, specialDir); + expect(spy).toHaveBeenCalled(); + expect(list.sort()).toEqual([testFile1, testFile2, testFile3].sort()); + spy.mockRestore(); + }); + } + test("copyObject", async () => { + let res = await storage.copyObject(ctx, testFile3, testFile4, specialDir, specialDir); + expect(res).toEqual(undefined); + // let buffer = Buffer.from(testFileData3); + // await storage.putObject(ctx, testFile3, buffer, buffer.length, specialDir); + let list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile1, testFile2, testFile3, testFile4].sort()); + }); + test("headObject", async () => { + let output; + output = await storage.headObject(ctx, testFile1, specialDir); + expect(output).toMatchObject({ContentLength: testFileData1.length}); + + output = await storage.headObject(ctx, testFile2, specialDir); + expect(output).toMatchObject({ContentLength: testFileData2.length}); + + output = await storage.headObject(ctx, testFile3, specialDir); + expect(output).toMatchObject({ContentLength: testFileData3.length}); + + output = await storage.headObject(ctx, testFile4, specialDir); + expect(output).toMatchObject({ContentLength: testFileData4.length}); + }); + test("getObject", async () => { + let output; + output = await storage.getObject(ctx, testFile1, specialDir); + expect(output.toString("utf8")).toEqual(testFileData1); + + output = await storage.getObject(ctx, testFile2, specialDir); + expect(output.toString("utf8")).toEqual(testFileData2); + + output = await storage.getObject(ctx, testFile3, specialDir); + expect(output.toString("utf8")).toEqual(testFileData3); + + output = await storage.getObject(ctx, testFile4, specialDir); + expect(output.toString("utf8")).toEqual(testFileData4); + }); + test("createReadStream", async () => { + let output, outputText; + + output = await storage.createReadStream(ctx, testFile1, specialDir); + await utils.sleep(100); + expect(output.contentLength).toEqual(testFileData1.length); + outputText = await utils.stream2Buffer(output.readStream); + await utils.sleep(100); + expect(outputText.toString("utf8")).toEqual(testFileData1); + + output = await storage.createReadStream(ctx, testFile2, specialDir); + expect(output.contentLength).toEqual(testFileData2.length); + outputText = await utils.stream2Buffer(output.readStream); + expect(outputText.toString("utf8")).toEqual(testFileData2); + + output = await storage.createReadStream(ctx, testFile3, specialDir); + expect(output.contentLength).toEqual(testFileData3.length); + outputText = await utils.stream2Buffer(output.readStream); + expect(outputText.toString("utf8")).toEqual(testFileData3); + }); + test("getSignedUrl", async () => { + let url, urls, data; + url = await storage.getSignedUrl(ctx, baseUrl, testFile1, urlType, undefined, undefined, specialDir); + data = await request(url); + expect(data).toEqual(testFileData1); + + url = await storage.getSignedUrl(ctx, baseUrl, testFile2, urlType, undefined, undefined, specialDir); + data = await request(url); + expect(data).toEqual(testFileData2); + + url = await storage.getSignedUrl(ctx, baseUrl, testFile3, urlType, undefined, undefined, specialDir); + data = await request(url); + expect(data).toEqual(testFileData3); + + url = await storage.getSignedUrl(ctx, baseUrl, testFile4, urlType, undefined, undefined, specialDir); + data = await request(url); + expect(data).toEqual(testFileData4); + }); + test("getSignedUrls", async () => { + let urls, data; + urls = await storage.getSignedUrls(ctx, baseUrl, testDir, urlType, undefined, specialDir); + data = []; + for(let i in urls) { + data.push(await request(urls[i])); + } + expect(data.sort()).toEqual([testFileData1, testFileData2, testFileData3, testFileData4].sort()); + }); + test("getSignedUrlsArrayByArray", async () => { + let urls, data; + urls = await storage.getSignedUrlsArrayByArray(ctx, baseUrl, [testFile1, testFile2], urlType, specialDir); + data = []; + for(let i = 0; i < urls.length; ++i) { + data.push(await request(urls[i])); + } + expect(data.sort()).toEqual([testFileData1, testFileData2].sort()); + }); + test("getSignedUrlsByArray", async () => { + let urls, data; + urls = await storage.getSignedUrlsByArray(ctx, baseUrl, [testFile3, testFile4], undefined, urlType, specialDir); + data = []; + for(let i in urls) { + data.push(await request(urls[i])); + } + expect(data.sort()).toEqual([testFileData3, testFileData4].sort()); + }); + test("deleteObject", async () => { + let list; + list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile1, testFile2, testFile3, testFile4].sort()); + + let res = await storage.deleteObject(ctx, testFile1, specialDir); + expect(res).toEqual(undefined); + + list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile2, testFile3, testFile4].sort()); + }); + test("deletePath", async () => { + let list; + list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([testFile2, testFile3, testFile4].sort()); + + let res = await storage.deletePath(ctx, testDir, specialDir); + expect(res).toEqual(undefined); + + list = await storage.listObjects(ctx, testDir, specialDir); + expect(list.sort()).toEqual([].sort()); + + tenantManager.setMultitenantMode(oldMultitenantMode); + }); +} + +// Assumed, that server is already up. +describe('storage common dir', function () { + runTestForDir(ctx, false, specialDirCache); +}); + +describe('storage forgotten dir', function () { + runTestForDir(ctx, false, specialDirForgotten); +}); + +describe('storage common dir with tenants', function () { + runTestForDir(ctx, true, specialDirCache); +}); + +describe('storage forgotten dir with tenants', function () { + runTestForDir(ctx, true, specialDirForgotten); +}); + +describe('storage mix common and forgotten dir', function () { + test("putObject", async () => { + tenantManager.setMultitenantMode(false); + + let buffer = Buffer.from(testFileData1); + let res = await storage.putObject(ctx, testFile1, buffer, buffer.length, specialDirCache); + expect(res).toEqual(undefined); + let list = await storage.listObjects(ctx, testDir, specialDirCache); + expect(list.sort()).toEqual([testFile1].sort()); + + buffer = Buffer.from(testFileData2); + res = await storage.putObject(ctx, testFile2, buffer, buffer.length, specialDirForgotten); + expect(res).toEqual(undefined); + list = await storage.listObjects(ctx, testDir, specialDirForgotten); + expect(list.sort()).toEqual([testFile2].sort()); + }); + + test("copyPath", async () => { + let list, res; + res = await storage.copyPath(ctx, testDir, testDir, specialDirCache, specialDirForgotten); + expect(res).toEqual(undefined); + + list = await storage.listObjects(ctx, testDir, specialDirForgotten); + expect(list.sort()).toEqual([testFile1, testFile2].sort()); + }); + test("copyObject", async () => { + let list, res; + res = await storage.copyObject(ctx, testFile2, testFile2, specialDirForgotten, specialDirCache); + expect(res).toEqual(undefined); + + list = await storage.listObjects(ctx, testDir, specialDirCache); + expect(list.sort()).toEqual([testFile1, testFile2].sort()); + }); + + test("deletePath", async () => { + let list, res; + res = await storage.deletePath(ctx, testDir, specialDirCache); + expect(res).toEqual(undefined); + + list = await storage.listObjects(ctx, testDir, specialDirCache); + expect(list.sort()).toEqual([].sort()); + + res = await storage.deletePath(ctx, testDir, specialDirForgotten); + expect(res).toEqual(undefined); + + list = await storage.listObjects(ctx, testDir, specialDirForgotten); + expect(list.sort()).toEqual([].sort()); + }); +}); diff --git a/tests/jest.config.js b/tests/jest.config.js new file mode 100644 index 0000000000..2f3ba967bd --- /dev/null +++ b/tests/jest.config.js @@ -0,0 +1,224 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +/* + * For a detailed explanation regarding each configuration property, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "", + + // Automatically clear mock calls, instances, contexts and results before every test + clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + // collectCoverage: false, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + // collectCoverageFrom: undefined, + + // The directory where Jest should output its coverage files + // coverageDirectory: undefined, + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: "v8", + + // A list of reporter names that Jest uses when writing coverage reports + // coverageReporters: [ + // "json", + // "text", + // "lcov", + // "clover" + // ], + + // An object that configures minimum threshold enforcement for coverage results + // coverageThreshold: undefined, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // The default configuration for fake timers + // fakeTimers: { + // "enableGlobally": false + // }, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "mjs", + // "cjs", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + // preset: undefined, + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // Automatically reset mock state before every test + // resetMocks: false, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // Automatically restore mock state and implementation before every test + // restoreMocks: false, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [""], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + setupFiles: ['./env-setup.js'], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: "jest-environment-node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + testMatch: [ + "**/?(*.)+(spec|tests).[tj]s?(x)" + ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "\\\\node_modules\\\\" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "\\\\node_modules\\\\", + // "\\.pnp\\.[^\\\\]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; diff --git a/tests/perf/checkFileExpire.js b/tests/perf/checkFileExpire.js new file mode 100644 index 0000000000..a6ec8bb86d --- /dev/null +++ b/tests/perf/checkFileExpire.js @@ -0,0 +1,138 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const { + createHistogram, + performance, + PerformanceObserver, +} = require('node:perf_hooks'); + +const co = require('co'); +const taskResult = require('./../../DocService/sources/taskresult'); +const storage = require('./../../Common/sources/storage-base'); +const storageFs = require('./../../Common/sources/storage-fs'); +const operationContext = require('./../../Common/sources/operationContext'); +const utils = require('./../../Common/sources/utils'); +const docsCoServer = require("./../../DocService/sources/DocsCoServer"); +const gc = require("./../../DocService/sources/gc"); + +let ctx = operationContext.global; + +let addRandomKeyTask; +let histograms = {}; + +async function beforeStart() { + let timerify = function (func, name) { + //todo remove anonymous functions. use func.name + Object.defineProperty(func, 'name', { + value: name + }); + let histogram = createHistogram(); + histograms[func.name] = histogram; + return performance.timerify(func, {histogram: histogram}); + } + + addRandomKeyTask = timerify(co.wrap(taskResult.addRandomKeyTask), "addRandomKeyTask"); + taskResult.getExpired = timerify(taskResult.getExpired, "getExpired"); + taskResult.remove = timerify(taskResult.remove, "remove"); + storage.putObject = timerify(storage.putObject, "putObject"); + storage.listObjects = timerify(storage.listObjects, "listObjects"); + storageFs.deletePath = timerify(storageFs.deletePath, "deletePath"); + storageFs.deleteObject = timerify(storageFs.deleteObject, "deleteObject"); + docsCoServer.getEditorsCountPromise = timerify(docsCoServer.getEditorsCountPromise, "getEditorsCountPromise"); + + const obs = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + let duration = Math.round(entry.duration * 1000) / 1000; + console.log(`${entry.name}:${duration}ms`); + }); + }); + obs.observe({ entryTypes: ['function']}); + + await docsCoServer.editorData.connect(); +} + +async function beforeEnd() { + let logHistogram = function (histogram, name) { + let mean = Math.round(histogram.mean / 1000) / 1000; + let min = Math.round(histogram.min / 1000) / 1000; + let max = Math.round(histogram.max / 1000) / 1000; + let count = histogram.count; + ctx.logger.info(`histogram ${name}: count=${count}, mean=${mean}ms, min=${min}ms, max=${max}ms`); + } + await utils.sleep(1000); + for (let name in histograms) { + logHistogram(histograms[name], name); + } +} + +async function addFileExpire(count, size, prefix, filesInFolder) { + while (count > 0) { + let task = await addRandomKeyTask(ctx, undefined, prefix, 8); + let data = Buffer.alloc(size, 0); + let rand = Math.floor(Math.random() * filesInFolder) + 1; + for (let i = 0; i < rand && count > 0; i++) { + await storage.putObject(ctx, `${task.key}/data${i}`, data, data.length); + count--; + } + } +} + +async function startTest() { + let args = process.argv.slice(2); + if (args.length < 4) { + ctx.logger.error('missing arguments.USAGE: checkFileExpire.js [add-files-count] [file-size-bytes] [key-prefix] [seconds-to-expire]'); + return; + } + ctx.logger.info("test started"); + await beforeStart(); + + await addFileExpire(parseInt(args[0]), parseInt(args[1]), args[2], parseInt(args[4] || 1)); + //delay to log observer events + await utils.sleep(1000); + await gc.checkFileExpire(args[3]); + + await beforeEnd(); + ctx.logger.info("test finished"); +} + +startTest().then(()=>{ + //delay to log observer events + return utils.sleep(1000); +}).catch((err) => { + ctx.logger.error(err.stack); +}).finally(() => { + process.exit(0); +}); diff --git a/tests/perf/convertImageToPng.js b/tests/perf/convertImageToPng.js new file mode 100644 index 0000000000..2e839d65f5 --- /dev/null +++ b/tests/perf/convertImageToPng.js @@ -0,0 +1,127 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2023 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const { + createHistogram, + performance, + PerformanceObserver, +} = require('node:perf_hooks'); + +const { readdir, mkdir, readFile, writeFile } = require("node:fs/promises"); +const path = require("path"); +// const Jimp = require('Jimp'); +const utils = require('./../../Common/sources/utils'); +const operationContext = require('./../../Common/sources/operationContext'); +const utilsDocService = require("./../../DocService/sources/utilsDocService"); + +let ctx = operationContext.global; + +let histograms = {}; + +async function beforeStart() { + let timerify = function (func) { + let histogram = createHistogram(); + histograms[func.name] = histogram; + return performance.timerify(func, {histogram: histogram}); + } + utilsDocService.convertImageToPng = timerify(utilsDocService.convertImageToPng); + // Jimp.read = timerify(Jimp.read); + + const obs = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + let duration = Math.round(entry.duration * 1000) / 1000; + console.log(`${entry.name}:${duration}ms`); + }); + }); + obs.observe({ entryTypes: ['function']}); +} + +async function beforeEnd() { + let logHistogram = function (histogram, name) { + let mean = Math.round(histogram.mean / 1000) / 1000; + let min = Math.round(histogram.min / 1000) / 1000; + let max = Math.round(histogram.max / 1000) / 1000; + let count = histogram.count; + ctx.logger.info(`histogram ${name}: count=${count}, mean=${mean}ms, min=${min}ms, max=${max}ms`); + } + await utils.sleep(1000); + for (let name in histograms) { + logHistogram(histograms[name], name); + } +} + +async function fixInDir(dirIn, dirOut) { + ctx.logger.info("dirIn:%s", dirIn); + ctx.logger.info("dirOut:%s", dirOut); + let dirents = await readdir(dirIn, {withFileTypes : true, recursive: true}); + for (let dirent of dirents) { + if (dirent.isFile()) { + let file = dirent.name; + ctx.logger.info("fixInDir:%s", file); + let buffer = await readFile(path.join(dirent.path, file)); + let bufferNew = await utilsDocService.convertImageToPng(ctx, buffer); + if (buffer !== bufferNew) { + let outputPath = path.join(dirOut, dirent.path.substring(dirIn.length), path.basename(file, path.extname(file)) + '.png'); + await mkdir(path.dirname(outputPath), {recursive: true}); + await writeFile(outputPath, bufferNew); + } + } + } +} + +async function startTest() { + let args = process.argv.slice(2); + if (args.length < 2) { + ctx.logger.error('missing arguments.USAGE: convertImageToPng.js "dirIn" "dirOut"'); + return; + } + ctx.logger.info("test started"); + await beforeStart(); + + + await fixInDir(args[0], args[1]); + + await beforeEnd(); + ctx.logger.info("test finished"); +} + +startTest().then(()=>{ + //delay to log observer events + return utils.sleep(1000); +}).catch((err) => { + ctx.logger.error(err.stack); +}).finally(() => { + process.exit(0); +}); diff --git a/tests/perf/fixImageExifRotation.js b/tests/perf/fixImageExifRotation.js new file mode 100644 index 0000000000..75111f3c30 --- /dev/null +++ b/tests/perf/fixImageExifRotation.js @@ -0,0 +1,127 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +'use strict'; + +const { + createHistogram, + performance, + PerformanceObserver, +} = require('node:perf_hooks'); + +const { readdir, mkdir, readFile, writeFile } = require("node:fs/promises"); +const path = require("path"); +// const Jimp = require('Jimp'); +const utils = require('./../../Common/sources/utils'); +const operationContext = require('./../../Common/sources/operationContext'); +const utilsDocService = require("./../../DocService/sources/utilsDocService"); + +let ctx = operationContext.global; + +let histograms = {}; + +async function beforeStart() { + let timerify = function (func) { + let histogram = createHistogram(); + histograms[func.name] = histogram; + return performance.timerify(func, {histogram: histogram}); + } + utilsDocService.fixImageExifRotation = timerify(utilsDocService.fixImageExifRotation); + // Jimp.read = timerify(Jimp.read); + + const obs = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + let duration = Math.round(entry.duration * 1000) / 1000; + console.log(`${entry.name}:${duration}ms`); + }); + }); + obs.observe({ entryTypes: ['function']}); +} + +async function beforeEnd() { + let logHistogram = function (histogram, name) { + let mean = Math.round(histogram.mean / 1000) / 1000; + let min = Math.round(histogram.min / 1000) / 1000; + let max = Math.round(histogram.max / 1000) / 1000; + let count = histogram.count; + ctx.logger.info(`histogram ${name}: count=${count}, mean=${mean}ms, min=${min}ms, max=${max}ms`); + } + await utils.sleep(1000); + for (let name in histograms) { + logHistogram(histograms[name], name); + } +} + +async function fixInDir(dirIn, dirOut) { + ctx.logger.info("dirIn:%s", dirIn); + ctx.logger.info("dirOut:%s", dirOut); + let dirents = await readdir(dirIn, {withFileTypes : true, recursive: true}); + for (let dirent of dirents) { + if (dirent.isFile()) { + let file = dirent.name; + ctx.logger.info("fixInDir:%s", file); + let buffer = await readFile(path.join(dirent.path, file)); + let bufferNew = await utilsDocService.fixImageExifRotation(ctx, buffer); + if (buffer !== bufferNew) { + let outputPath = path.join(dirOut, dirent.path.substring(dirIn.length), file); + await mkdir(path.dirname(outputPath), {recursive: true}); + await writeFile(outputPath, bufferNew); + } + } + } +} + +async function startTest() { + let args = process.argv.slice(2); + if (args.length < 2) { + ctx.logger.error('missing arguments.USAGE: fixImageExifRotation.js "dirIn" "dirOut"'); + return; + } + ctx.logger.info("test started"); + await beforeStart(); + + + await fixInDir(args[0], args[1]); + + await beforeEnd(); + ctx.logger.info("test finished"); +} + +startTest().then(()=>{ + //delay to log observer events + return utils.sleep(1000); +}).catch((err) => { + ctx.logger.error(err.stack); +}).finally(() => { + process.exit(0); +}); diff --git a/tests/unit/mailService.tests.js b/tests/unit/mailService.tests.js new file mode 100644 index 0000000000..2bbb76fda4 --- /dev/null +++ b/tests/unit/mailService.tests.js @@ -0,0 +1,52 @@ +const { describe, test, expect, afterAll } = require('@jest/globals'); +const nodemailer = require('../../Common/node_modules/nodemailer'); + +const operationContext = require('../../Common/sources/operationContext'); +const mailService = require('../../Common/sources/mailService'); + +const ctx = new operationContext.Context(); +const defaultTestSMTPServer = { + host: 'smtp.ethereal.email', + port: 587 +}; +const testTimeout = 1000 * 10; + +afterAll(function () { + mailService.transportersRelease(); +}) + +describe('Mail service', function () { + describe('SMTP', function () { + const { host, port } = defaultTestSMTPServer; + + test('Transporters life cycle', async function () { + // Accounts created at https://ethereal.email/, all messages in tests goes here: https://ethereal.email/messages + // Ethereial is a special SMTP sever for mailing tests in collaboration with Nodemailer. + const accounts = await Promise.all([nodemailer.createTestAccount(), nodemailer.createTestAccount(), nodemailer.createTestAccount()]); + const auth = accounts.map(account => { return { user: account.user, pass: account.pass }}); + auth.forEach(credential => mailService.createTransporter(ctx, host, port, credential, { from: 'some.mail@ethereal.com' })); + + for (let i = 0; i < auth.length; i++) { + const credentials = auth[i]; + const mail = await mailService.send( + host, + credentials.user, + { to: `some.recipient@server${i + 1}.com`, text: 'simple test text', subject: 'Mail service test' } + ); + + expect(mail.envelope).toEqual({ from: 'some.mail@ethereal.com', to: [`some.recipient@server${i + 1}.com`] }); + } + + const accountToBeDeleted = auth[1]; + mailService.deleteTransporter(ctx, host, accountToBeDeleted.user); + + const errorPromise = mailService.send( + host, + accountToBeDeleted.user, + { to: 'no.recipient@server.com', text: 'simple test text', subject: 'Mail service test' } + ); + + await expect(errorPromise).rejects.toThrow(); + }, testTimeout); + }); +}); diff --git a/tests/unit/sample.tests.js b/tests/unit/sample.tests.js new file mode 100644 index 0000000000..a00755abb3 --- /dev/null +++ b/tests/unit/sample.tests.js @@ -0,0 +1,43 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const { describe, test, expect } = require('@jest/globals'); + +describe('Successful and failure tests', function () { + test('Successful test', function () { + expect(true).toBeTruthy(); + }); + + test.skip('Failure test', function () { + expect(true).toBeFalsy(); + }); +}); \ No newline at end of file diff --git a/tests/unit/utils.tests.js b/tests/unit/utils.tests.js new file mode 100644 index 0000000000..08880c0c68 --- /dev/null +++ b/tests/unit/utils.tests.js @@ -0,0 +1,62 @@ +/* + * (c) Copyright Ascensio System SIA 2010-2024 + * + * This program is a free software product. You can redistribute it and/or + * modify it under the terms of the GNU Affero General Public License (AGPL) + * version 3 as published by the Free Software Foundation. In accordance with + * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect + * that Ascensio System SIA expressly excludes the warranty of non-infringement + * of any third-party rights. + * + * This program is distributed WITHOUT ANY WARRANTY; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For + * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html + * + * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish + * street, Riga, Latvia, EU, LV-1050. + * + * The interactive user interfaces in modified source and object code versions + * of the Program must display Appropriate Legal Notices, as required under + * Section 5 of the GNU AGPL version 3. + * + * Pursuant to Section 7(b) of the License you must retain the original Product + * logo when distributing the program. Pursuant to Section 7(e) we decline to + * grant you any rights under trademark law for use of our trademarks. + * + * All the Product's GUI elements, including illustrations and icon sets, as + * well as technical writing 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 + * + */ + +const { describe, test, expect } = require('@jest/globals'); +const config = require('../../Common/node_modules/config'); + +const operationContext = require('../../Common/sources/operationContext'); +const utils = require('../../Common/sources/utils'); + +const ctx = new operationContext.Context(); +const minimumIterationsByteLength = 4; + + +describe('AES encryption & decryption', function () { + test('Iterations range', async function () { + const configuration = config.get('aesEncrypt.config'); + const encrypted = await utils.encryptPassword(ctx, 'secretstring'); + const { iterationsByteLength = 5 } = configuration; + + const [iterationsHex] = encrypted.split(':'); + const iterations = parseInt(iterationsHex, 16); + + const iterationsLength = iterationsByteLength < minimumIterationsByteLength ? minimumIterationsByteLength : iterationsByteLength; + expect(iterations).toBeGreaterThanOrEqual(Math.pow(10, iterationsLength - 1)); + expect(iterations).toBeLessThanOrEqual(Math.pow(10, iterationsLength) - 1); + }); + + test('Correct workflow', async function () { + const encrypted = await utils.encryptPassword(ctx, 'secretstring'); + const decrypted = await utils.decryptPassword(ctx, encrypted); + expect(decrypted).toEqual('secretstring'); + }); +});