commit e604c7e9c912a4748d3ffdeaf6468617a5a0c731 Author: Joseph B. McQueen Date: Wed Jul 1 17:53:07 2026 -0400 Initial commit: multi-bot CollabCentral Extends the single-Novi codebase into a multi-bot mass-messenger where each bot has its own token, avatar, label, and per-user authorization. - Secrets moved out of config.json: per-bot tokens in gitignored config/botTokens.json (with enabled flag), service-account OAuth in gitignored config/token.json (rewritten by refresh cron), integration and Google keys in .env. - Single Webex integration handles OAuth for all bots via a per-app redirect URI derived from OAUTH_CALLBACK_URL_TEMPLATE. - New requireBot middleware and getBotConfig helper reject requests for unknown or disabled bots at the /CollabCentral/:app boundary. - New /info endpoint plus dynamic frontend loading (sendMessage, monitorJobs) so pages self-describe per bot, including bot avatar fetched from Webex /people/me at startup. - Job draft state keyed by cookieId + appName so each bot has its own building queue; job list/detail endpoints filter by appName so users only see jobs from bots they are authorized on. - New jobDetail page for a readable per-job view; completed jobs are retained for 30 days by the cleanup cron. - Completion adaptive cards use per-bot avatar and label. - Miscellaneous fixes: off-by-two in the send loop, removed three dead send/process variants, added defensive init for jobs.* on load, dropped the deprecated crypto npm shim, and cleaned up stray logger labels and typos. Co-authored-by: Cursor diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6c78bea --- /dev/null +++ b/.dockerignore @@ -0,0 +1,14 @@ +node_modules +logs +uploads + +# Config is mounted at runtime (-v ./config:/usr/src/app/config). Never bake it +# into the image, especially secrets like .env / botTokens.json / token.json. +.env +config + +# Misc +testData.json +.DS_Store +*.log +.git diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..508a8b6 --- /dev/null +++ b/.env.example @@ -0,0 +1,26 @@ +# --- Server ---------------------------------------------------------------- +SERVER_PORT=1450 + +# --- OAuth callback URL template ------------------------------------------ +# The `:app` placeholder is replaced per-request with the bot's appName. +# IMPORTANT: every bot's resolved URL must be registered as an allowed +# redirect URI in the Webex integration portal +# (developer.webex.com -> My Apps -> -> Redirect URIs). +# +# Example: with the template below and bots `novi` and `techupdates`, register: +# https://bot.example.com/CollabCentral/novi/oauth +# https://bot.example.com/CollabCentral/techupdates/oauth +# +# When you add a new bot, register its redirect URI before users try to log in. +OAUTH_CALLBACK_URL_TEMPLATE=https://bot.example.com/CollabCentral/:app/oauth + +# --- Webex integration (single integration used by all bots' OAuth) ------- +WEBEX_INTEGRATION_CLIENT_ID=your-integration-client-id +WEBEX_INTEGRATION_CLIENT_SECRET=your-integration-client-secret + +# --- Webex service account (used for group/people lookups) ---------------- +WEBEX_SERVICE_ACCOUNT_CLIENT_ID=your-service-account-client-id +WEBEX_SERVICE_ACCOUNT_CLIENT_SECRET=your-service-account-client-secret + +# --- Google Translate ----------------------------------------------------- +GOOGLE_TRANSLATE_API_KEY=your-google-translate-api-key diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7201b99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +node_modules/ + +# Secrets +.env +config/botTokens.json +config/token.json + +# Runtime data +config/jobs.json +config/jobs copy.json +config/userPrefs.json +uploads/* +!uploads/.gitkeep + +# Misc +testData.json +.DS_Store +*.log +logs/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8de75f8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:20 +# Create app directory +WORKDIR /usr/src/app + +# Install app dependencies +# A wildcard is used to ensure both package.json AND package-lock.json are copied +# where available (npm@5+) +COPY package*.json ./ + +RUN npm install +# If you are building your code for production +# RUN npm ci --only=production + +# Bundle app source +COPY . . + +EXPOSE 1450 + +CMD [ "node", "index.js" ] diff --git a/config/botTokens.example.json b/config/botTokens.example.json new file mode 100644 index 0000000..50df5e5 --- /dev/null +++ b/config/botTokens.example.json @@ -0,0 +1,10 @@ +{ + "novi": { + "token": "REPLACE-WITH-NOVI-BOT-TOKEN", + "enabled": true + }, + "techupdates": { + "token": "REPLACE-WITH-TECHUPDATES-BOT-TOKEN", + "enabled": false + } +} diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..4391135 --- /dev/null +++ b/config/config.json @@ -0,0 +1,265 @@ +{ + "server": { + "name": "CollabCentral", + "port": "1450" + }, + "languages": [], + "webex": { + "bot": { + "novi": { + "label": "Novi Communicator", + "authorized": { + "Y2lzY29zcGFyazovL3VzL1BFT1BMRS80NzAyNDlhNC1kNjFjLTQzNmMtYTE1My1kOGUzZTExMmI4MDU": { + "id": "Y2lzY29zcGFyazovL3VzL1BFT1BMRS80NzAyNDlhNC1kNjFjLTQzNmMtYTE1My1kOGUzZTExMmI4MDU", + "displayName": "Joe McQueen", + "email": "mcqueenj@ae.com", + "avatar": "https://avatar-prod-us-east-2.webexcontent.com/Avtr~V1~db9cdc31-8f8e-40b4-916c-8d9e9c92e70d/V1~667e3673b56f4ddc39139c73da9140dd631eb821a756524ead12e37e3a61afc7~9c5b6e767cff46e3af35a003cea38f25~1600", + "groups": [ + { + "name": "Group: Audio Visual", + "alias": "AV Team", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZTQ3NmY3MjktZWViYi00MDI3LWFlMTctNzc1YWI1ZDgzYmNhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: South Side Campus", + "alias": "SSW Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvYjAxOWM5MzgtNTZhNi00NTQxLTk0NjgtYjFmOWEwM2I5NTNlOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: IT Warrendale", + "alias": "Warrendale", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMjkwN2RlZGUtZjJmNi00OTdjLTkwMjItZTRmOGIzNzM3MTFhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: New York Design Office Personnel", + "alias": "NY Design Center", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjUzOGQzYmYtY2UzOS00Y2ZlLWFmZWUtNGViYTM0OGQ3Y2FjOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: 34th Street Office Personnel", + "alias": "34th Street", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZGVmOTA0MDMtMTBlZS00YzRlLWIzYWItYzNjZDlkODhmMTQ1OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: San Francisco Personnel", + "alias": "San Francisco Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjJjYTcxYTktYWYzYy00YjQ4LWIxZjUtMjc1NDEwNGYwMWJlOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Ottawa Campus", + "alias": "Ottawa Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZWIyYWI1ZDgtYzk2Ny00MmUwLTg0OWQtZGE5OTYzMDdkNDhhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hazleton Campus", + "alias": "Hazleton Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMDc3NTI1NGEtZTIxMS00ZTQ1LTgxNjktNjYxYTAzOGYxNTE3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Canada Distribution Center", + "alias": "Canada DC", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZmUwMDk1MjUtMzEyZC00YzMxLTgwMzItNWEyODIwODRiYTVjOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Mexico Office Personnel", + "alias": "Mexico Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjhmYTJiZGUtNjI2My00NWEwLTljMDMtNjMyMGI0NmVlODE0OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hong Kong Office", + "alias": "Hong Kong Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMjdlM2QzZGEtZjI4Ny00NDg3LWI3NzUtM2JmZTA3MzNhNzZhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hazleton Mgmt", + "alias": "Hazleton Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvYTEzODI3OTgtOWY5Zi00YmY1LTlkMmQtODNhZTk2OGY0MjU3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Ottawa Campus Mgmt", + "alias": "Ottawa Campus Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZDYwNzFhYmMtYzNjZC00ZDdkLWFiYTktOGY0NDRiZTViODQ3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: MDC DC Mgmt", + "alias": "Canada DC Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvOTk2ZWRmNzYtNjdlYS00MjdkLWEzNGEtMTBjNjJhYmRkMDM5OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + } + ] + }, + "Y2lzY29zcGFyazovL3VzL1BFT1BMRS84MWUzMzExMi1jYWZlLTQ0NWYtOGE5OS01MDI3Mzg4ZDQwNGM": { + "id": "Y2lzY29zcGFyazovL3VzL1BFT1BMRS84MWUzMzExMi1jYWZlLTQ0NWYtOGE5OS01MDI3Mzg4ZDQwNGM", + "displayName": "Megan Reinecker", + "email": "reineckerm@ae.com", + "avatar": "https://avatar-prod-us-east-2.webexcontent.com/Avtr~V1~db9cdc31-8f8e-40b4-916c-8d9e9c92e70d/V1~81e33112-cafe-445f-8a99-5027388d404c~c5234572b50e49ff884c45b3ea0e3fea~1600", + "groups": [ + { + "name": "Group: South Side Campus", + "alias": "SSW Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvYjAxOWM5MzgtNTZhNi00NTQxLTk0NjgtYjFmOWEwM2I5NTNlOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: IT Warrendale", + "alias": "Warrendale", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMjkwN2RlZGUtZjJmNi00OTdjLTkwMjItZTRmOGIzNzM3MTFhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: New York Design Office Personnel", + "alias": "NY Design Center", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjUzOGQzYmYtY2UzOS00Y2ZlLWFmZWUtNGViYTM0OGQ3Y2FjOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: 34th Street Office Personnel", + "alias": "34th Street", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZGVmOTA0MDMtMTBlZS00YzRlLWIzYWItYzNjZDlkODhmMTQ1OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: San Francisco Personnel", + "alias": "San Francisco Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjJjYTcxYTktYWYzYy00YjQ4LWIxZjUtMjc1NDEwNGYwMWJlOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Ottawa Campus", + "alias": "Ottawa Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZWIyYWI1ZDgtYzk2Ny00MmUwLTg0OWQtZGE5OTYzMDdkNDhhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hazleton Campus", + "alias": "Hazleton Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMDc3NTI1NGEtZTIxMS00ZTQ1LTgxNjktNjYxYTAzOGYxNTE3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Canada Distribution Center", + "alias": "Canada DC", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZmUwMDk1MjUtMzEyZC00YzMxLTgwMzItNWEyODIwODRiYTVjOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Mexico Office Personnel", + "alias": "Mexico Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjhmYTJiZGUtNjI2My00NWEwLTljMDMtNjMyMGI0NmVlODE0OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hong Kong Office", + "alias": "Hong Kong Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMjdlM2QzZGEtZjI4Ny00NDg3LWI3NzUtM2JmZTA3MzNhNzZhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hazleton Mgmt", + "alias": "Hazleton Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvYTEzODI3OTgtOWY5Zi00YmY1LTlkMmQtODNhZTk2OGY0MjU3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Ottawa Campus Mgmt", + "alias": "Ottawa Campus Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZDYwNzFhYmMtYzNjZC00ZDdkLWFiYTktOGY0NDRiZTViODQ3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: MDC DC Mgmt", + "alias": "Canada DC Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvOTk2ZWRmNzYtNjdlYS00MjdkLWEzNGEtMTBjNjJhYmRkMDM5OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + } + ] + }, + "Y2lzY29zcGFyazovL3VzL1BFT1BMRS80NmZkYWEzYy03YTNkLTRjY2QtYWU4Ny0xYzIzNmIyNGE0Zjg": { + "id": "Y2lzY29zcGFyazovL3VzL1BFT1BMRS80NmZkYWEzYy03YTNkLTRjY2QtYWU4Ny0xYzIzNmIyNGE0Zjg", + "displayName": "Jayna Catalina", + "email": "catalinaj@ae.com", + "avatar": "https://avatar-prod-us-east-2.webexcontent.com/Avtr~V1~db9cdc31-8f8e-40b4-916c-8d9e9c92e70d/V1~cf803b9485e536eece6e2f2e541f8ab8691867a0765656a0ebb28ba4b74d7bca~8afff234e4f045ad9fa92af6f8d082c0~1600", + "groups": [ + { + "name": "Group: South Side Campus", + "alias": "SSW Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvYjAxOWM5MzgtNTZhNi00NTQxLTk0NjgtYjFmOWEwM2I5NTNlOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: IT Warrendale", + "alias": "Warrendale", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMjkwN2RlZGUtZjJmNi00OTdjLTkwMjItZTRmOGIzNzM3MTFhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: New York Design Office Personnel", + "alias": "NY Design Center", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjUzOGQzYmYtY2UzOS00Y2ZlLWFmZWUtNGViYTM0OGQ3Y2FjOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: 34th Street Office Personnel", + "alias": "34th Street", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZGVmOTA0MDMtMTBlZS00YzRlLWIzYWItYzNjZDlkODhmMTQ1OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: San Francisco Personnel", + "alias": "San Francisco Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjJjYTcxYTktYWYzYy00YjQ4LWIxZjUtMjc1NDEwNGYwMWJlOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Ottawa Campus", + "alias": "Ottawa Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZWIyYWI1ZDgtYzk2Ny00MmUwLTg0OWQtZGE5OTYzMDdkNDhhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hazleton Campus", + "alias": "Hazleton Campus", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMDc3NTI1NGEtZTIxMS00ZTQ1LTgxNjktNjYxYTAzOGYxNTE3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Canada Distribution Center", + "alias": "Canada DC", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZmUwMDk1MjUtMzEyZC00YzMxLTgwMzItNWEyODIwODRiYTVjOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Mexico Office Personnel", + "alias": "Mexico Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvNjhmYTJiZGUtNjI2My00NWEwLTljMDMtNjMyMGI0NmVlODE0OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hong Kong Office", + "alias": "Hong Kong Office", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvMjdlM2QzZGEtZjI4Ny00NDg3LWI3NzUtM2JmZTA3MzNhNzZhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Hazleton Mgmt", + "alias": "Hazleton Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvYTEzODI3OTgtOWY5Zi00YmY1LTlkMmQtODNhZTk2OGY0MjU3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: Ottawa Campus Mgmt", + "alias": "Ottawa Campus Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZDYwNzFhYmMtYzNjZC00ZDdkLWFiYTktOGY0NDRiZTViODQ3OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + }, + { + "name": "Group: MDC DC Mgmt", + "alias": "Canada DC Mgmt", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvOTk2ZWRmNzYtNjdlYS00MjdkLWEzNGEtMTBjNjJhYmRkMDM5OmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + } + ] + } + } + }, + "techupdates": { + "label": "Technology Updates", + "authorized": { + "Y2lzY29zcGFyazovL3VzL1BFT1BMRS80NzAyNDlhNC1kNjFjLTQzNmMtYTE1My1kOGUzZTExMmI4MDU": { + "id": "Y2lzY29zcGFyazovL3VzL1BFT1BMRS80NzAyNDlhNC1kNjFjLTQzNmMtYTE1My1kOGUzZTExMmI4MDU", + "displayName": "Joe McQueen", + "email": "mcqueenj@ae.com", + "avatar": "https://avatar-prod-us-east-2.webexcontent.com/Avtr~V1~db9cdc31-8f8e-40b4-916c-8d9e9c92e70d/V1~667e3673b56f4ddc39139c73da9140dd631eb821a756524ead12e37e3a61afc7~9c5b6e767cff46e3af35a003cea38f25~1600", + "groups": [ + { + "name": "Group: Audio Visual", + "alias": "AV Team", + "id": "Y2lzY29zcGFyazovL3VzL1NDSU1fR1JPVVAvZTQ3NmY3MjktZWViYi00MDI3LWFlMTctNzc1YWI1ZDgzYmNhOmRiOWNkYzMxLThmOGUtNDBiNC05MTZjLThkOWU5YzkyZTcwZA" + } + ] + } + } + } + }, + "integration": { + "integrationId": "Y2lzY29zcGFyazovL3VzL0FQUExJQ0FUSU9OL0MzZTQ5NDUwZGQ0ZGMxNmY0OWQxNjEyYTVjNGRiZWNhZjY2M2I1ODdmZWEyOGYzYjMwY2QwOGQzNThkNTNjMDQ1", + "integrationName": "CollabCentral" + }, + "serviceAccount": { + "appName": "CollabCentral" + } + } +} \ No newline at end of file diff --git a/config/languages.json b/config/languages.json new file mode 100644 index 0000000..71faf0d --- /dev/null +++ b/config/languages.json @@ -0,0 +1,18 @@ +[ + { + "name": "spanish", + "key": "es" + }, + { + "name": "french", + "key": "fr" + }, + { + "name": "japanese", + "key": "ja" + }, + { + "name": "chinese", + "key": "zh" + } +] \ No newline at end of file diff --git a/html/DirectoryConnector.zip b/html/DirectoryConnector.zip new file mode 100644 index 0000000..a6a4d1c Binary files /dev/null and b/html/DirectoryConnector.zip differ diff --git a/html/aeoPropsBot.png b/html/aeoPropsBot.png new file mode 100644 index 0000000..d8c3a68 Binary files /dev/null and b/html/aeoPropsBot.png differ diff --git a/html/auth.js b/html/auth.js new file mode 100644 index 0000000..8aa656d --- /dev/null +++ b/html/auth.js @@ -0,0 +1,57 @@ +//console.log(window.location.pathname); +var pathArray = window.location.pathname.split('/'); +//console.log(pathArray); +//console.log(pathArray); +var appName = pathArray[2]; +//console.log("appName=" + appName); +/* +if (appName == "novi") { + setCookie("appLabel", "Novi Communicator"); +} else if (appName == "techupdates") { + setCookie("appLabel", "Technology Updates"); +} else { + setCookie("appLabel", "Unknown"); +} +setCookie("appName",appName,1); +*/ + +if (getCookie("id")) { + console.log("Found ID Cookie: " + getCookie("id")); + window.location.href = "/CollabCentral/" + appName + "/sendMessage.html"; +} else { + console.log("No ID Cookie found."); + + var randomNumber = Math.random().toString(); + randomNumber = randomNumber.substring(2, randomNumber.length); + fetch('/CollabCentral/' + appName + '/authUrl') + .then(res => res.text()) + .then((res) => { + window.location.href = res + randomNumber; + }); +} + +function getCookie(cname) { + let name = cname + "="; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} + +function setCookie(name, value, days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + (value || "") + expires + "; path=/"; + } \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..804cd56 --- /dev/null +++ b/html/index.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/html/jobDetail.html b/html/jobDetail.html new file mode 100644 index 0000000..2a5c330 --- /dev/null +++ b/html/jobDetail.html @@ -0,0 +1,165 @@ + + + + CollabCentral + + + + + + + + +
+
+ +
+
+
Back to Jobs
+
+
+ + + + + + + + + + + diff --git a/html/jobDetail.js b/html/jobDetail.js new file mode 100644 index 0000000..b5f2638 --- /dev/null +++ b/html/jobDetail.js @@ -0,0 +1,179 @@ +var pathArray = window.location.pathname.split('/'); +var appName = pathArray[2]; + +var urlParams = new URLSearchParams(window.location.search); +var jobId = urlParams.get('jobId'); + +if (!getCookie("id")) { + var randomNumber = Math.random().toString().substring(2); + fetch('/CollabCentral/' + appName + '/authUrl') + .then(res => res.text()) + .then(res => { window.location.href = res + randomNumber; }); +} else { + bootstrap(); +} + +function bootstrap() { + fetch('/CollabCentral/' + appName + '/info') + .then(res => res.json()) + .then(info => { + document.title = info.label + " — Job #" + (jobId || "?"); + document.getElementById("appLabel").innerHTML = info.label; + document.getElementById("name").innerHTML = getCookie("displayName") || ""; + document.getElementById("appName").href = info.faviconUrl; + document.getElementById("jobLink").href = "/CollabCentral/" + appName + "/monitorJobs.html"; + + var appIcon = document.getElementById("appIcon"); + appIcon.src = info.iconUrl; + appIcon.style.maxHeight = "150px"; + appIcon.style.maxWidth = "150px"; + + if (!info.authorized) { + document.getElementById("unauthorizedPanel").classList.remove("hidden"); + return; + } + loadJobDetail(); + }) + .catch(err => console.error("Failed to load /info:", err)); +} + +function loadJobDetail() { + if (!jobId) { + document.getElementById("notFoundPanel").classList.remove("hidden"); + return; + } + fetch('/CollabCentral/' + appName + '/jobs/detail/' + encodeURIComponent(jobId)) + .then(res => { + if (res.status === 404) { + document.getElementById("notFoundPanel").classList.remove("hidden"); + return null; + } + if (!res.ok) throw new Error("HTTP " + res.status); + return res.json(); + }) + .then(job => { + if (!job) return; + renderJob(job); + document.getElementById("detailPanel").classList.remove("hidden"); + }) + .catch(err => { + console.error("Failed to load job detail:", err); + document.getElementById("notFoundPanel").classList.remove("hidden"); + }); +} + +function renderJob(job) { + document.getElementById("jobTitle").textContent = "Job #" + (job.jobId || "?"); + var subtitle = "Sent by " + (job.senderDisplayName || "unknown"); + if (job.startTime) subtitle += " · started " + formatDate(job.startTime); + document.getElementById("subtitle").textContent = subtitle; + + var dl = document.getElementById("summaryGrid"); + dl.innerHTML = ""; + var stats = job.stats || {}; + var rows = [ + ["Job ID", job.jobId], + ["Submitted by", job.senderDisplayName], + ["Scheduled for", job.scheduledFor ? formatDate(job.scheduledFor) : "Sent immediately"], + ["Started", formatDate(job.startTime)], + ["Completed", job.endTime ? formatDate(job.endTime) : "In progress"], + ["Total duration", stats.totalTime], + ["Webex time", stats.webexTime], + ["Average per message", stats.averageTime], + ["Recipients", (job.memberList || []).length], + ["Delivered", formatSuccess(stats)] + ]; + for (var i = 0; i < rows.length; i++) { + var key = rows[i][0], val = rows[i][1]; + if (val === undefined || val === null || val === "") continue; + var dt = document.createElement("dt"); dt.textContent = key; + var dd = document.createElement("dd"); dd.textContent = val; + dl.appendChild(dt); dl.appendChild(dd); + } + + var preview = document.getElementById("messagePreview"); + var msg = (job.message && job.message.english) || job.message || {}; + if (msg.html) { + preview.innerHTML = msg.html; + } else if (msg.markdown) { + preview.textContent = msg.markdown; + } else if (msg.text) { + preview.textContent = msg.text; + } else if (typeof msg.raw === "string") { + preview.textContent = msg.raw; + } else { + preview.innerHTML = "No message content available."; + } + + var members = job.memberList || []; + document.getElementById("recipientCount").textContent = members.length; + + $('#recipients').dataTable({ + data: members.map(formatRecipientRow), + columns: [ + { data: 'displayName' }, + { data: 'email' }, + { data: 'status' }, + { data: 'time' }, + { data: 'error' } + ], + pageLength: 50, + lengthMenu: [25, 50, 100, 250], + order: [[2, 'asc']], + searching: true, + info: true, + paging: true + }); +} + +function formatRecipientRow(m) { + var result = m.results || null; + var status; + var email = "—"; + var err = ""; + + if (!result) { + status = 'Pending'; + } else if (result.id && !result.message && !result.errors) { + status = 'Delivered'; + email = result.toPersonEmail || email; + } else { + status = 'Failed'; + email = result.toPersonEmail || email; + if (result.message) err = result.message; + else if (result.errors && result.errors.length) { + err = result.errors.map(function (e) { return e.description || e.code || JSON.stringify(e); }).join("; "); + } + } + + return { + displayName: m.displayName || "—", + email: email, + status: status, + time: m.msgTime ? (m.msgTime / 1000).toFixed(2) + " s" : "—", + error: err + }; +} + +function formatSuccess(stats) { + if (!stats || stats.succeededMsgs === undefined) return null; + var pct = (typeof stats.succeededMsgPct === "number") ? stats.succeededMsgPct.toFixed(1) : stats.succeededMsgPct; + return stats.succeededMsgs + " / " + stats.totalMsgs + " (" + pct + "%)"; +} + +function formatDate(d) { + if (!d) return null; + try { return new Date(d).toLocaleString(); } catch (e) { return d; } +} + +function getCookie(cname) { + let name = cname + "="; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') c = c.substring(1); + if (c.indexOf(name) == 0) return c.substring(name.length, c.length); + } + return ""; +} diff --git a/html/js/virtual-select.min.css b/html/js/virtual-select.min.css new file mode 100644 index 0000000..868242f --- /dev/null +++ b/html/js/virtual-select.min.css @@ -0,0 +1,13 @@ +/*! + * Virtual Select v1.0.44 + * https://sa-si-dev.github.io/virtual-select + * Licensed under MIT (https://github.com/sa-si-dev/virtual-select/blob/master/LICENSE) + */ +@keyframes vscomp-animation-spin{to{transform:rotateZ(360deg)}}.vscomp-popup-active{overflow:hidden !important}.vscomp-ele{display:inline-block;max-width:250px;width:100%}.vscomp-wrapper{color:#333;display:inline-flex;flex-wrap:wrap;font-family:sans-serif;font-size:14px;position:relative;text-align:left;width:100%}.vscomp-wrapper *,.vscomp-wrapper *::before,.vscomp-wrapper *::after{box-sizing:border-box}.vscomp-wrapper:focus{outline:none}.vscomp-dropbox-wrapper{left:0;position:absolute;top:0}.vscomp-toggle-button{align-items:center;background-color:#fff;border:1px solid #ddd;cursor:pointer;display:flex;padding:7px 30px 7px 10px;position:relative;width:100%}.vscomp-value{height:20px;line-height:20px;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vscomp-arrow{align-items:center;display:flex;height:100%;justify-content:center;position:absolute;right:0;top:0;width:30px}.vscomp-arrow::after{transform:rotate(45deg);border:1px solid rgba(0,0,0,0);border-bottom-color:#111;border-right-color:#111;content:"";height:8px;margin-top:-6px;width:8px}.vscomp-clear-icon{height:12px;position:relative;width:12px}.vscomp-clear-icon::before,.vscomp-clear-icon::after{background-color:#999;content:"";height:12px;left:5px;position:absolute;top:0;width:2px}.vscomp-clear-icon::before{transform:rotate(45deg)}.vscomp-clear-icon::after{transform:rotate(-45deg)}.vscomp-clear-icon:hover::before,.vscomp-clear-icon:hover::after{background:#333}.vscomp-clear-button{align-items:center;border-radius:50%;display:none;height:24px;justify-content:center;margin-top:-12px;position:absolute;right:30px;top:50%;width:24px}.vscomp-clear-button:hover{background:#ccc}.vscomp-clear-button:hover .vscomp-clear-icon::before,.vscomp-clear-button:hover .vscomp-clear-icon::after{background-color:#333}.vscomp-dropbox-close-button{align-items:center;background-color:#fff;border-radius:50%;bottom:-48px;cursor:pointer;display:none;height:40px;justify-content:center;left:50%;margin-left:-20px;position:absolute;width:40px}.vscomp-value-tag.more-value-count{white-space:nowrap}.vscomp-dropbox-container{width:100%;z-index:2}.vscomp-dropbox{background-color:#fff;width:100%}.vscomp-options-container{max-height:210px;overflow:auto;position:relative}.vscomp-options-bottom-freezer{bottom:0;height:2px;left:0;position:absolute;right:0}.vscomp-option{align-items:center;cursor:pointer;display:flex;flex-wrap:wrap;height:40px;padding:0 15px;position:relative}.vscomp-option.selected{background-color:#eee}.vscomp-option.focused{background-color:#ccc}.vscomp-option.disabled{cursor:default;opacity:.5}.vscomp-option.group-title .vscomp-option-text{cursor:default;opacity:.6}.vscomp-option.group-title.selected{background-color:rgba(0,0,0,0)}.vscomp-option.group-option{padding-left:30px}.vscomp-new-option-icon{height:30px;position:absolute;right:0;top:0;width:30px}.vscomp-new-option-icon::before{border:15px solid #512da8;border-bottom-color:rgba(0,0,0,0);border-left-color:rgba(0,0,0,0);content:"";position:absolute;right:0;top:0}.vscomp-new-option-icon::after{align-items:center;color:#fff;content:"+";display:flex;font-size:18px;height:15px;justify-content:center;position:absolute;right:1px;top:0;width:15px}.vscomp-option-text{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.vscomp-option-description{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#666;font-size:13px;line-height:15px;width:100%}.vscomp-search-container{align-items:center;border-bottom:1px solid #ddd;display:flex;height:40px;padding:0 5px 0 15px;position:relative}.vscomp-search-label,.vscomp-live-region,.vscomp-dropbox-container-top,.vscomp-dropbox-container-bottom{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.vscomp-search-input{background-color:rgba(0,0,0,0);border:0;color:inherit;font-size:15px;height:38px;padding:10px 0;width:calc(100% - 30px)}.vscomp-search-input:focus{outline:none}.vscomp-search-clear{align-items:center;color:#999;cursor:pointer;display:flex;font-size:25px;height:30px;justify-content:center;-webkit-user-select:none;-moz-user-select:none;user-select:none;visibility:hidden;width:30px}.vscomp-search-clear:hover{color:inherit}.vscomp-no-options,.vscomp-no-search-results{align-items:center;display:none;justify-content:center;padding:20px 10px}.vscomp-options-loader{display:none;padding:20px 0;text-align:center}.vscomp-options-loader::before{animation:vscomp-animation-spin .8s infinite linear;background-color:#fff;border-radius:50%;box-shadow:-4px -5px 3px -3px rgba(0,0,0,.3);content:"";display:inline-block;height:40px;opacity:.7;width:40px}.vscomp-ele[disabled]{cursor:not-allowed;-webkit-user-select:none;-moz-user-select:none;user-select:none}.vscomp-ele[disabled] .vscomp-wrapper{opacity:.7;pointer-events:none}.vscomp-wrapper .checkbox-icon{display:inline-flex;height:15px;margin-right:10px;position:relative;width:15px}.vscomp-wrapper .checkbox-icon::after{transition-duration:.2s;border:2px solid #888;content:"";display:inline-block;height:100%;width:100%}.vscomp-wrapper .checkbox-icon.checked::after{transform:rotate(45deg) translate(1px, -4px);border-color:#512da8;border-left-color:rgba(0,0,0,0);border-top-color:rgba(0,0,0,0);width:50%}.vscomp-wrapper.show-as-popup .vscomp-dropbox-container{align-items:center;background-color:rgba(0,0,0,.5);display:flex;height:100vh;justify-content:center;left:0;opacity:1;overflow:auto;padding:0 10px;position:fixed;top:0;width:100vw}.vscomp-wrapper.show-as-popup .vscomp-dropbox{margin-top:-24px;max-height:calc(80% - 48px);max-width:500px;position:relative;width:80%}.vscomp-wrapper.show-as-popup .vscomp-dropbox-close-button{display:flex}.vscomp-wrapper.popup-position-left .vscomp-dropbox-container{justify-content:flex-start}.vscomp-wrapper.popup-position-right .vscomp-dropbox-container{justify-content:flex-end}.vscomp-wrapper.has-select-all .vscomp-toggle-all-button{align-items:center;cursor:pointer;display:flex}.vscomp-wrapper.has-select-all .vscomp-search-input,.vscomp-wrapper.has-select-all .vscomp-toggle-all-label{width:calc(100% - 55px)}.vscomp-wrapper.has-select-all .vscomp-toggle-all-label{display:none}.vscomp-wrapper:not(.has-search-input) .vscomp-toggle-all-button{width:100%}.vscomp-wrapper:not(.has-search-input) .vscomp-toggle-all-label{display:inline-block}.vscomp-wrapper.multiple .vscomp-option .vscomp-option-text{width:calc(100% - 25px)}.vscomp-wrapper.multiple .vscomp-option .vscomp-option-description{padding-left:25px}.vscomp-wrapper.multiple .vscomp-option.selected .checkbox-icon::after{transform:rotate(45deg) translate(1px, -4px);border-color:#512da8;border-left-color:rgba(0,0,0,0);border-top-color:rgba(0,0,0,0);width:50%}.vscomp-wrapper.focused .vscomp-toggle-button,.vscomp-wrapper:focus .vscomp-toggle-button{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12),0 1px 5px 0 rgba(0,0,0,.2)}.vscomp-wrapper.closed .vscomp-dropbox-container,.vscomp-wrapper.closed.vscomp-dropbox-wrapper{display:none}.vscomp-wrapper:not(.has-value) .vscomp-value{opacity:.5}.vscomp-wrapper.has-clear-button.has-value .vscomp-clear-button{display:flex}.vscomp-wrapper.has-clear-button .vscomp-toggle-button{padding-right:54px}.vscomp-wrapper.has-no-options .vscomp-options-container,.vscomp-wrapper.has-no-search-results .vscomp-options-container{display:none}.vscomp-wrapper.has-no-options .vscomp-no-options{display:flex}.vscomp-wrapper.has-no-search-results .vscomp-no-search-results{display:flex}.vscomp-wrapper.has-search-value .vscomp-search-clear{visibility:visible}.vscomp-wrapper.has-no-options .vscomp-toggle-all-button{opacity:.5;pointer-events:none}.vscomp-wrapper.keep-always-open .vscomp-toggle-button{padding-right:24px}.vscomp-wrapper.keep-always-open .vscomp-clear-button{right:5px}.vscomp-wrapper.keep-always-open .vscomp-arrow{display:none}.vscomp-wrapper.keep-always-open .vscomp-dropbox-container{position:relative;z-index:1}.vscomp-wrapper.keep-always-open .vscomp-dropbox{transition-duration:0s;border:1px solid #ddd;box-shadow:none}.vscomp-wrapper.keep-always-open.focused,.vscomp-wrapper.keep-always-open:focus,.vscomp-wrapper.keep-always-open:hover{box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12),0 1px 5px 0 rgba(0,0,0,.2)}.vscomp-wrapper.server-searching .vscomp-options-list{display:none}.vscomp-wrapper.server-searching .vscomp-options-loader{display:block}.vscomp-wrapper.has-error .vscomp-toggle-button{border-color:#b00020}.vscomp-wrapper.show-value-as-tags .vscomp-toggle-button{padding:4px 22px 0 10px}.vscomp-wrapper.show-value-as-tags .vscomp-value{display:flex;flex-wrap:wrap;height:auto;min-height:28px;overflow:auto;text-overflow:unset;white-space:normal}.vscomp-wrapper.show-value-as-tags .vscomp-value-tag{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;align-items:center;border:1px solid #ddd;border-radius:20px;display:inline-flex;font-size:12px;line-height:16px;margin:0 4px 4px 0;max-width:100%;padding:2px 3px 2px 8px}.vscomp-wrapper.show-value-as-tags .vscomp-value-tag.more-value-count{padding-right:8px}.vscomp-wrapper.show-value-as-tags .vscomp-value-tag-content{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;width:calc(100% - 20px)}.vscomp-wrapper.show-value-as-tags .vscomp-value-tag-clear-button{align-items:center;display:flex;height:20px;justify-content:center;width:20px}.vscomp-wrapper.show-value-as-tags .vscomp-value-tag-clear-button .vscomp-clear-icon{transform:scale(0.8)}.vscomp-wrapper.show-value-as-tags .vscomp-arrow{height:34px}.vscomp-wrapper.show-value-as-tags .vscomp-clear-button{margin-top:0;top:5px}.vscomp-wrapper.show-value-as-tags.has-value .vscomp-arrow{display:none}.vscomp-wrapper.show-value-as-tags.has-value .vscomp-clear-button{right:2px}.vscomp-wrapper.show-value-as-tags:not(.has-value) .vscomp-toggle-button{padding-bottom:2px}.vscomp-wrapper.show-value-as-tags:not(.has-value) .vscomp-value{align-items:center;padding-bottom:3px}.vscomp-wrapper.text-direction-rtl{direction:rtl}.vscomp-wrapper.text-direction-rtl .vscomp-toggle-button{padding:7px 10px 7px 30px}.vscomp-wrapper.text-direction-rtl .vscomp-arrow{left:0;right:auto}.vscomp-wrapper.text-direction-rtl .vscomp-clear-button{left:30px;right:auto !important}.vscomp-wrapper.text-direction-rtl .checkbox-icon{margin-left:10px;margin-right:0}.vscomp-wrapper.text-direction-rtl .checkbox-icon.checked::after{transform:rotate(45deg) translate(-4px, 1px)}.vscomp-wrapper.text-direction-rtl .vscomp-search-container{padding:0 15px 0 5px}.vscomp-wrapper.text-direction-rtl .vscomp-toggle-all-label{text-align:right}.vscomp-wrapper.text-direction-rtl .vscomp-option{text-align:right}.vscomp-wrapper.text-direction-rtl .vscomp-option.group-option{padding-right:30px}.vscomp-wrapper.text-direction-rtl .vscomp-new-option-icon{left:0;right:auto}.vscomp-wrapper.text-direction-rtl .vscomp-new-option-icon::before{border-left-color:#512da8;border-right-color:rgba(0,0,0,0)}.vscomp-wrapper.text-direction-rtl .vscomp-new-option-icon::after{left:1px;right:auto}.vscomp-wrapper.text-direction-rtl.multiple .vscomp-option.selected .checkbox-icon::after{transform:rotate(45deg) translate(-4px, 1px)}.vscomp-wrapper.text-direction-rtl.has-clear-button .vscomp-toggle-button{padding-left:54px}.vscomp-wrapper.text-direction-rtl.keep-always-open .vscomp-toggle-button{padding-left:24px}.vscomp-wrapper.text-direction-rtl.keep-always-open .vscomp-clear-button{left:5px}.vscomp-wrapper.text-direction-rtl.show-value-as-tags .vscomp-toggle-button{padding:4px 10px 0 22px}.vscomp-wrapper.text-direction-rtl.show-value-as-tags .vscomp-value-tag{margin:0 0 4px 4px;padding:2px 8px 2px 3px}.vscomp-wrapper.text-direction-rtl.show-value-as-tags .vscomp-value-tag.more-value-count{padding-left:8px}.vscomp-wrapper.text-direction-rtl.show-value-as-tags.has-value .vscomp-clear-button{left:2px} +/*! + * Popover v1.0.13 + * https://sa-si-dev.github.io/popover + * Licensed under MIT (https://github.com/sa-si-dev/popover/blob/master/LICENSE) + */ +.pop-comp-wrapper{display:none;position:absolute;top:0;left:0;opacity:0;color:#000;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12),0 1px 5px 0 rgba(0,0,0,.2);text-align:left;flex-wrap:wrap;z-index:1}.pop-comp-arrow{position:absolute;z-index:1;width:16px;height:16px;overflow:hidden;pointer-events:none}.pop-comp-arrow::before{content:"";position:absolute;top:8px;left:8px;width:16px;height:16px;background-color:#fff;box-shadow:0 2px 2px 0 rgba(0,0,0,.14),0 3px 1px -2px rgba(0,0,0,.12),0 1px 5px 0 rgba(0,0,0,.2);-webkit-transform-origin:left top;transform-origin:left top;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.pop-comp-content{position:relative;z-index:2}.pop-comp-wrapper.position-bottom>.pop-comp-arrow{margin-left:-8px;left:0;top:-15px}.pop-comp-wrapper.position-bottom>.pop-comp-arrow::before{box-shadow:0px 0px 2px 0 rgba(0,0,0,.14)}.pop-comp-wrapper.position-top>.pop-comp-arrow{margin-left:-8px;left:0;bottom:-15px}.pop-comp-wrapper.position-right>.pop-comp-arrow{margin-top:-8px;top:0;left:-15px}.pop-comp-wrapper.position-left>.pop-comp-arrow{margin-top:-8px;top:0;right:-15px}.pop-comp-disable-events{pointer-events:none} + diff --git a/html/js/virtual-select.min.js b/html/js/virtual-select.min.js new file mode 100644 index 0000000..63f86ca --- /dev/null +++ b/html/js/virtual-select.min.js @@ -0,0 +1,5 @@ +/*! + * Virtual Select v1.0.44 + * https://sa-si-dev.github.io/virtual-select + * Licensed under MIT (https://github.com/sa-si-dev/virtual-select/blob/master/LICENSE) + */!function(){"use strict";function e(e){return function(e){if(Array.isArray(e))return t(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,i){if(e){if("string"==typeof e)return t(e,i);var o=Object.prototype.toString.call(e).slice(8,-1);return"Object"===o&&e.constructor&&(o=e.constructor.name),"Map"===o||"Set"===o?Array.from(e):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?t(e,i):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var i=0,o=new Array(t);i1&&void 0!==arguments[1]&&arguments[1]}},{key:"isEmpty",value:function(e){var t=!1;return e?Array.isArray(e)?0===e.length&&(t=!0):"object"===i(e)&&0===Object.keys(e).length&&(t=!0):t=!0,t}},{key:"isNotEmpty",value:function(e){return!this.isEmpty(e)}},{key:"removeItemFromArray",value:function(t,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(!Array.isArray(t)||!t.length)return t;var s=o?e(t):t,n=s.indexOf(i);return-1!==n&&s.splice(n,1),s}},{key:"removeArrayEmpty",value:function(e){return Array.isArray(e)&&e.length?e.filter((function(e){return!!e})):[]}},{key:"getRandomInt",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,i=Math.ceil(t),o=Math.floor(e);return Math.floor(Math.random()*(o-i-1))+i}},{key:"regexEscape",value:function(e){return e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&")}},{key:"normalizeString",value:function(e){return e.normalize("NFD").replace(/[^\w]/g,"")}},{key:"willTextOverflow",value:function(e,t){var i=document.createElement("div");i.style.position="absolute",i.style.visibility="hidden",i.style.whiteSpace="nowrap",i.style.fontSize=window.getComputedStyle(e).fontSize,i.style.fontFamily=window.getComputedStyle(e).fontFamily,i.textContent=t,document.body.appendChild(i);var o=i.clientWidth;return document.body.removeChild(i),o>e.clientWidth}},{key:"replaceDoubleQuotesWithHTML",value:function(e){return e.replace(/"/g,""")}},{key:"containsHTML",value:function(e){return/<[a-z][\s\S]*>/i.test(e)}}],(n=null)&&o(s.prototype,n),r&&o(s,r),Object.defineProperty(s,"prototype",{writable:!1}),t}();function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function r(e,t){return function(e){if(Array.isArray(e))return e}(e)||function(e,t){var i=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=i){var o,s,n,r,a=[],l=!0,u=!1;try{if(n=(i=i.call(e)).next,0===t){if(Object(i)!==i)return;l=!1}else for(;!(l=(o=n.call(i)).done)&&(a.push(o.value),a.length!==t);l=!0);}catch(e){u=!0,s=e}finally{try{if(!l&&null!=i.return&&(r=i.return(),Object(r)!==r))return}finally{if(u)throw s}}return a}}(e,t)||l(e,t)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(e){return function(e){if(Array.isArray(e))return u(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||l(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function l(e,t){if(e){if("string"==typeof e)return u(e,t);var i=Object.prototype.toString.call(e).slice(8,-1);return"Object"===i&&e.constructor&&(i=e.constructor.name),"Map"===i||"Set"===i?Array.from(e):"Arguments"===i||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(i)?u(e,t):void 0}}function u(e,t){(null==t||t>e.length)&&(t=e.length);for(var i=0,o=new Array(t);ie.offsetWidth}},{key:"getData",value:function(e,t,i){if(e){var o=e?e.dataset[t]:"";return"number"===i?o=parseFloat(o)||0:"true"===o?o=!0:"false"===o&&(o=!1),o}}},{key:"setData",value:function(e,t,i){e&&(e.dataset[t]=i)}},{key:"setAttr",value:function(e,t,i){e&&e.setAttribute(t,i)}},{key:"setAttrFromEle",value:function(e,t,i,o){var s={};i.forEach((function(t){s[t]=e.getAttribute(t)})),i.forEach((function(e){var i=s[e];(i||-1!==o.indexOf(e)&&""===i)&&t.setAttribute(e,i)}))}},{key:"setStyle",value:function(e,t,i){e&&(e.style[t]=i)}},{key:"setStyles",value:function(e,t){e&&t&&Object.keys(t).forEach((function(i){e.style[i]=t[i]}))}},{key:"setAria",value:function(e,t,i){var o=t;"role"!==o&&(o="aria-".concat(o)),e.setAttribute(o,i)}},{key:"getElements",value:function(e){return e?void 0===e.forEach?[e]:e:[]}},{key:"getElementsBySelector",value:function(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:void 0,o=void 0!==i?i:document;return""!==t&&(e=o.querySelectorAll(t)),void 0!==e?Array.from(e):[]}},{key:"addEvent",value:function(t,i,o){t&&s.removeArrayEmpty(i.split(" ")).forEach((function(i){e.getElements(t).forEach((function(e){e.addEventListener(i,o)}))}))}},{key:"dispatchEvent",value:function(t,i){var o=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(t){var s=e.getElements(t);setTimeout((function(){s.forEach((function(e){e.dispatchEvent(new CustomEvent(i,{bubbles:o}))}))}),0)}}},{key:"getAttributesText",value:function(e){var t="";return e?(Object.entries(e).forEach((function(e){var i=r(e,2),o=i[0],s=i[1];void 0!==s&&(t+=" ".concat(o,'="').concat(s,'" '))})),t):t}},{key:"convertPropToDataAttr",value:function(e){return e?"data-".concat(e).replace(/([A-Z])/g,"-$1").toLowerCase():""}},{key:"changeTabIndex",value:function(t,i){t?e.getElements(t).forEach((function(e){e.tabIndex=i})):console.log(t,"Invalid element provided.")}},{key:"removeEvent",value:function(t,i,o){t&&e.getElements(t).forEach((function(e){e.removeEventListener(i,o)}))}}],(i=null)&&c(t.prototype,i),o&&c(t,o),Object.defineProperty(t,"prototype",{writable:!1}),e}();function h(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),i.push.apply(i,o)}return i}function d(e){for(var t=1;te.length)&&(t=e.length);for(var i=0,o=new Array(t);i\n \n
\n
\n ").concat(this.placeholder,'\n
\n
\n
\n \n
\n
\n\n ').concat(this.renderDropbox({wrapperClasses:t}),"\n ");this.$ele.innerHTML=l,this.$body=document.querySelector("body"),this.$wrapper=this.$ele.querySelector(".vscomp-wrapper"),this.hasDropboxWrapper?(this.$allWrappers=[this.$wrapper,this.$dropboxWrapper],this.$dropboxContainer=this.$dropboxWrapper.querySelector(".vscomp-dropbox-container"),p.addClass(this.$dropboxContainer,"pop-comp-wrapper")):(this.$allWrappers=[this.$wrapper],this.$dropboxContainer=this.$wrapper.querySelector(".vscomp-dropbox-container")),this.$toggleButton=this.$ele.querySelector(".vscomp-toggle-button"),this.$clearButton=this.$ele.querySelector(".vscomp-clear-button"),this.$valueText=this.$ele.querySelector(".vscomp-value"),this.$hiddenInput=this.$ele.querySelector(".vscomp-hidden-input"),this.$dropbox=this.$dropboxContainer.querySelector(".vscomp-dropbox"),this.$dropboxCloseButton=this.$dropboxContainer.querySelector(".vscomp-dropbox-close-button"),this.$dropboxContainerBottom=this.$dropboxContainer.querySelector(".vscomp-dropbox-container-bottom"),this.$dropboxContainerTop=this.$dropboxContainer.querySelector(".vscomp-dropbox-container-top"),this.$search=this.$dropboxContainer.querySelector(".vscomp-search-wrapper"),this.$optionsContainer=this.$dropboxContainer.querySelector(".vscomp-options-container"),this.$optionsList=this.$dropboxContainer.querySelector(".vscomp-options-list"),this.$options=this.$dropboxContainer.querySelector(".vscomp-options"),this.$noOptions=this.$dropboxContainer.querySelector(".vscomp-no-options"),this.$noSearchResults=this.$dropboxContainer.querySelector(".vscomp-no-search-results"),this.afterRenderWrapper()}}},{key:"renderDropbox",value:function(e){var t=e.wrapperClasses,i="self"!==this.dropboxWrapper?document.querySelector(this.dropboxWrapper):null,o='
\n \n
\n
\n\n
\n
\n\n
\n
\n
\n
\n\n
\n
').concat(this.noOptionsText,'
\n
').concat(this.noSearchResultsText,'
\n\n \n
\n \n
');if(i){var s=document.createElement("div");return this.$dropboxWrapper=s,this.hasDropboxWrapper=!0,s.innerHTML=o,i.appendChild(s),p.addClass(s,"vscomp-dropbox-wrapper ".concat(t)),""}return this.hasDropboxWrapper=!1,o}},{key:"renderOptions",value:function(){var e,t=this,i="",o=this.getVisibleOptions(),n="",r="",a=!(!this.markSearchResults||!this.searchValue),l=this.labelRenderer,u=this.disableOptionGroupCheckbox,c=this.uniqueId,p=this.searchGroup,h="function"==typeof l,d=s.convertToBoolean,v="";if(a&&(e=new RegExp("(".concat(s.regexEscape(this.searchValue),")(?!([^<]+)?>)"),"gi")),this.multiple&&(n=''),this.allowNewOption){var f=this.getTooltipAttrText("New Option");r='")}o.forEach((function(o){var s,f=o.index,y="vscomp-option",b=t.getTooltipAttrText("",!0,!0),m=n,g="",O="",S="",x="",w="-1",k=d(o.isSelected),E="";if(o.classNames&&(y+=" ".concat(o.classNames)),o.isFocused&&(w="0",y+=" focused"),o.isDisabled&&(y+=" disabled",E='aria-disabled="true"'),o.isGroupTitle&&(v=o.label,y+=" group-title",u&&(m="")),k&&(y+=" selected"),o.isGroupOption){var C="";y+=" group-option",S='data-group-index="'.concat(o.groupIndex,'"'),o.customData?(v=void 0!==o.customData.group_name?"".concat(o.customData.group_name,", "):"",C=void 0!==o.customData.description?" ".concat(o.customData.description,","):"",x='aria-label="'.concat(v," ").concat(o.label,", ").concat(C,'"')):x='aria-label="'.concat(v,", ").concat(o.label,'"')}s=h?l(o):o.label,o.description&&(O='
").concat(o.description,"
")),o.isCurrentNew?(y+=" current-new",g+=r):!a||o.isGroupTitle&&!p||(s=s.replace(e,"$1")),i+='
\n ").concat(s,"\n \n ").concat(O,"\n ").concat(g,"\n
")})),v="",this.$options.innerHTML=i,this.$visibleOptions=this.$options.querySelectorAll(".vscomp-option"),this.afterRenderOptions()}},{key:"renderSearch",value:function(){if(this.hasSearchContainer){var e="",t="";this.multiple&&!this.disableSelectAll&&(e='\n \n '.concat(this.selectAllText,"\n ")),this.hasSearch&&(t='\n \n ×'));var i='
\n '.concat(e,"\n ").concat(t,"\n
");this.$search.innerHTML=i,this.$searchInput=this.$dropboxContainer.querySelector(".vscomp-search-input"),this.$searchClear=this.$dropboxContainer.querySelector(".vscomp-search-clear"),this.$toggleAllButton=this.$dropboxContainer.querySelector(".vscomp-toggle-all-button"),this.$toggleAllCheckbox=this.$dropboxContainer.querySelector(".vscomp-toggle-all-checkbox"),this.addEvent(this.$searchInput,"input","onSearch"),this.addEvent(this.$searchClear,"click","onSearchClear"),this.addEvent(this.$toggleAllButton,"click","onToggleAllOptions"),this.addEvent(this.$dropboxContainerBottom,"focus","onDropboxContainerTopOrBottomFocus"),this.addEvent(this.$dropboxContainerTop,"focus","onDropboxContainerTopOrBottomFocus")}}},{key:"addEvents",value:function(){this.addEvent(document,"click","onDocumentClick"),this.addEvent(this.$allWrappers,"keydown","onKeyDown"),this.addEvent(this.$toggleButton,"click","onToggleButtonClick"),this.addEvent(this.$clearButton,"click keydown","onClearButtonClick"),this.addEvent(this.$dropboxContainer,"click","onDropboxContainerClick"),this.addEvent(this.$dropboxCloseButton,"click","onDropboxCloseButtonClick"),this.addEvent(this.$optionsContainer,"scroll","onOptionsScroll"),this.addEvent(this.$options,"click","onOptionsClick"),this.addEvent(this.$options,"mouseover","onOptionsMouseOver"),this.addEvent(this.$options,"touchmove","onOptionsTouchMove"),this.addMutationObserver()}},{key:"addEvent",value:function(e,t,i){var o=this;e&&s.removeArrayEmpty(t.split(" ")).forEach((function(t){var s="".concat(i,"-").concat(t),n=o.events[s];n||(n=o[i].bind(o),o.events[s]=n),p.addEvent(e,t,n)}))}},{key:"removeEvents",value:function(){this.removeEvent(document,"click","onDocumentClick"),this.removeEvent(this.$allWrappers,"keydown","onKeyDown"),this.removeEvent(this.$toggleButton,"click","onToggleButtonClick"),this.removeEvent(this.$clearButton,"click keydown","onClearButtonClick"),this.removeEvent(this.$dropboxContainer,"click","onDropboxContainerClick"),this.removeEvent(this.$dropboxCloseButton,"click","onDropboxCloseButtonClick"),this.removeEvent(this.$optionsContainer,"scroll","onOptionsScroll"),this.removeEvent(this.$options,"click","onOptionsClick"),this.removeEvent(this.$options,"mouseover","onOptionsMouseOver"),this.removeEvent(this.$options,"touchmove","onOptionsTouchMove"),this.removeMutationObserver()}},{key:"removeEvent",value:function(e,t,i){var o=this;e&&s.removeArrayEmpty(t.split(" ")).forEach((function(t){var s="".concat(i,"-").concat(t),n=o.events[s];n&&p.removeEvent(e,t,n)}))}},{key:"onDocumentClick",value:function(e){var t=e.target.closest(".vscomp-wrapper");t!==this.$wrapper&&t!==this.$dropboxWrapper&&this.isOpened()&&this.closeDropbox()}},{key:"onKeyDown",value:function(e){var t=e.which||e.keyCode,i=w[t];return document.activeElement===this.$searchInput&&e.shiftKey&&9===t?(e.preventDefault(),void this.$dropboxContainerTop.focus()):document.activeElement===this.$searchInput&&9===t?(e.preventDefault(),void this.focusFirstVisibleOption()):void(document.activeElement!==this.$wrapper||27!==t&&"Escape"!==e.key||!this.showAsPopup?i&&this[i](e):this.closeDropbox())}},{key:"onEnterPress",value:function(e){e.preventDefault(),this.isOpened()?this.selectFocusedOption():!1===this.$ele.disabled&&this.openDropbox()}},{key:"onDownArrowPress",value:function(e){e.preventDefault(),this.isOpened()?this.focusOption({direction:"next"}):this.openDropbox()}},{key:"onUpArrowPress",value:function(e){e.preventDefault(),this.isOpened()?this.focusOption({direction:"previous"}):this.openDropbox()}},{key:"onBackspaceOrDeletePress",value:function(e){e.target===this.$wrapper&&(e.preventDefault(),this.selectedValues.length>0&&this.reset())}},{key:"onToggleButtonClick",value:function(e){var t=e.target;t.closest(".vscomp-value-tag-clear-button")?this.removeValue(t.closest(".vscomp-value-tag")):t.closest(".toggle-button-child")||this.toggleDropbox()}},{key:"onClearButtonClick",value:function(e){"click"===e.type?this.reset():"keydown"!==e.type||"Enter"!==e.code&&"Space"!==e.code||(e.stopPropagation(),this.reset())}},{key:"onOptionsScroll",value:function(){this.setVisibleOptions()}},{key:"onOptionsClick",value:function(e){var t=e.target.closest(".vscomp-option");t&&!p.hasClass(t,"disabled")&&(p.hasClass(t,"group-title")?this.onGroupTitleClick(t):this.selectOption(t,{event:e}))}},{key:"onGroupTitleClick",value:function(e){if(e&&this.multiple&&!this.disableOptionGroupCheckbox){var t=!p.hasClass(e,"selected");this.toggleGroupTitleCheckbox(e,t),this.toggleGroupOptions(e,t)}}},{key:"onDropboxContainerClick",value:function(e){e.target.closest(".vscomp-dropbox")||this.closeDropbox()}},{key:"onDropboxCloseButtonClick",value:function(){this.closeDropbox()}},{key:"onOptionsMouseOver",value:function(e){var t=e.target.closest(".vscomp-option");t&&this.isOpened()&&(p.hasClass(t,"disabled")||p.hasClass(t,"group-title")?this.removeOptionFocus():this.focusOption({$option:t}))}},{key:"onOptionsTouchMove",value:function(){this.removeOptionFocus()}},{key:"onSearch",value:function(e){e.stopPropagation(),this.setSearchValue(e.target.value,!0)}},{key:"onSearchClear",value:function(){this.setSearchValue(""),this.focusSearchInput()}},{key:"onToggleAllOptions",value:function(){this.toggleAllOptions()}},{key:"onDropboxContainerTopOrBottomFocus",value:function(){this.closeDropbox()}},{key:"onResize",value:function(){this.setOptionsContainerHeight(!0)}},{key:"addMutationObserver",value:function(){var e=this;if(this.hasDropboxWrapper){var t=this.$ele;this.mutationObserver=new MutationObserver((function(i){var o=!1,s=!1;i.forEach((function(e){o||(o=b(e.addedNodes).some((function(e){return!(e!==t&&!e.contains(t))}))),s||(s=b(e.removedNodes).some((function(e){return!(e!==t&&!e.contains(t))})))})),s&&!o&&e.destroy()})),this.mutationObserver.observe(document.querySelector("body"),{childList:!0,subtree:!0})}}},{key:"removeMutationObserver",value:function(){this.mutationObserver.disconnect()}},{key:"beforeValueSet",value:function(e){this.toggleAllOptionsClass(!e&&void 0)}},{key:"beforeSelectNewValue",value:function(){var e=this,t=this.getNewOption(),i=t.index;this.newValues.push(t.value),this.setOptionProp(i,"isCurrentNew",!1),this.setOptionProp(i,"isNew",!0),setTimeout((function(){e.setSearchValue(""),e.focusSearchInput()}),0)}},{key:"afterRenderWrapper",value:function(){p.addClass(this.$ele,"vscomp-ele"),this.renderSearch(),this.setEleStyles(),this.setDropboxStyles(),this.setOptionsHeight(),this.setVisibleOptions(),this.setOptionsContainerHeight(),this.addEvents(),this.setEleProps(),this.keepAlwaysOpen||this.showAsPopup||this.initDropboxPopover(),this.initialSelectedValue?this.setValueMethod(this.initialSelectedValue,this.silentInitialValueSet):this.autoSelectFirstOption&&this.visibleOptions.length&&this.setValueMethod(this.visibleOptions[0].value,this.silentInitialValueSet),this.showOptionsOnlyOnSearch&&this.setSearchValue("",!1,!0),this.initialDisabled&&this.disable(),this.autofocus&&this.focus()}},{key:"afterRenderOptions",value:function(){var e=this.getVisibleOptions(),t=!this.options.length&&!this.hasServerSearch,i=!t&&!e.length;if((!this.allowNewOption||this.hasServerSearch||this.showOptionsOnlyOnSearch)&&(p.toggleClass(this.$allWrappers,"has-no-search-results",i),i?(p.setAttr(this.$noSearchResults,"tabindex","0"),p.setAttr(this.$noSearchResults,"aria-hidden","false")):(p.setAttr(this.$noSearchResults,"tabindex","-1"),p.setAttr(this.$noSearchResults,"aria-hidden","true"))),p.toggleClass(this.$allWrappers,"has-no-options",t),t?(p.setAttr(this.$noOptions,"tabindex","0"),p.setAttr(this.$noOptions,"aria-hidden","false")):(p.setAttr(this.$noOptions,"tabindex","-1"),p.setAttr(this.$noOptions,"aria-hidden","true")),this.setOptionAttr(),this.setOptionsPosition(),this.setOptionsTooltip(),document.activeElement!==this.$searchInput){var o=p.getElementsBySelector(".focused",this.$dropboxContainer)[0];void 0!==o&&o.focus()}}},{key:"afterSetOptionsContainerHeight",value:function(e){e&&this.showAsPopup&&this.setVisibleOptions()}},{key:"afterSetSearchValue",value:function(){var e=this;this.hasServerSearch?(clearInterval(this.serverSearchTimeout),this.serverSearchTimeout=setTimeout((function(){e.serverSearch()}),this.searchDelay)):this.setVisibleOptionsCount(),this.selectAllOnlyVisible&&this.toggleAllOptionsClass(),this.focusOption({focusFirst:!0})}},{key:"afterSetVisibleOptionsCount",value:function(){this.scrollToTop(),this.setOptionsHeight(),this.setVisibleOptions(),this.updatePosition()}},{key:"afterValueSet",value:function(){this.scrollToTop(),this.setSearchValue(""),this.renderOptions()}},{key:"afterSetOptions",value:function(e){e&&this.setSelectedProp(),this.setOptionsHeight(),this.setVisibleOptions(),this.showOptionsOnlyOnSearch&&this.setSearchValue("",!1,!0),e||this.reset()}},{key:"setProps",value:function(e){var t=this.setDefaultProps(e);this.setPropsFromElementAttr(t);var i=s.convertToBoolean;this.$ele=t.ele,this.dropboxWrapper=t.dropboxWrapper,this.valueKey=t.valueKey,this.labelKey=t.labelKey,this.descriptionKey=t.descriptionKey,this.aliasKey=t.aliasKey,this.optionHeightText=t.optionHeight,this.optionHeight=parseFloat(this.optionHeightText),this.multiple=i(t.multiple),this.hasSearch=i(t.search),this.searchByStartsWith=i(t.searchByStartsWith),this.searchGroup=i(t.searchGroup),this.hideClearButton=i(t.hideClearButton),this.autoSelectFirstOption=i(t.autoSelectFirstOption),this.hasOptionDescription=i(t.hasOptionDescription),this.silentInitialValueSet=i(t.silentInitialValueSet),this.allowNewOption=i(t.allowNewOption),this.markSearchResults=i(t.markSearchResults),this.showSelectedOptionsFirst=i(t.showSelectedOptionsFirst),this.disableSelectAll=i(t.disableSelectAll),this.keepAlwaysOpen=i(t.keepAlwaysOpen),this.showDropboxAsPopup=i(t.showDropboxAsPopup),this.hideValueTooltipOnSelectAll=i(t.hideValueTooltipOnSelectAll),this.showOptionsOnlyOnSearch=i(t.showOptionsOnlyOnSearch),this.selectAllOnlyVisible=i(t.selectAllOnlyVisible),this.alwaysShowSelectedOptionsCount=i(t.alwaysShowSelectedOptionsCount),this.alwaysShowSelectedOptionsLabel=i(t.alwaysShowSelectedOptionsLabel),this.disableAllOptionsSelectedText=i(t.disableAllOptionsSelectedText),this.showValueAsTags=i(t.showValueAsTags),this.disableOptionGroupCheckbox=i(t.disableOptionGroupCheckbox),this.enableSecureText=i(t.enableSecureText),this.setValueAsArray=i(t.setValueAsArray),this.disableValidation=i(t.disableValidation),this.initialDisabled=i(t.disabled),this.required=i(t.required),this.autofocus=i(t.autofocus),this.useGroupValue=i(t.useGroupValue),this.focusSelectedOptionOnOpen=i(t.focusSelectedOptionOnOpen),this.noOptionsText=t.noOptionsText,this.noSearchResultsText=t.noSearchResultsText,this.selectAllText=t.selectAllText,this.searchNormalize=t.searchNormalize,this.searchPlaceholderText=t.searchPlaceholderText,this.searchFormLabel=t.searchFormLabel,this.optionsSelectedText=t.optionsSelectedText,this.optionSelectedText=t.optionSelectedText,this.allOptionsSelectedText=t.allOptionsSelectedText,this.clearButtonText=t.clearButtonText,this.moreText=t.moreText,this.placeholder=t.placeholder,this.position=t.position,this.textDirection=t.textDirection,this.dropboxWidth=t.dropboxWidth,this.tooltipFontSize=t.tooltipFontSize,this.tooltipAlignment=t.tooltipAlignment,this.tooltipMaxWidth=t.tooltipMaxWidth,this.updatePositionThrottle=t.updatePositionThrottle,this.noOfDisplayValues=parseInt(t.noOfDisplayValues),this.zIndex=parseInt(t.zIndex),this.maxValues=parseInt(t.maxValues),this.minValues=parseInt(t.minValues),this.name=this.secureText(t.name),this.additionalClasses=t.additionalClasses,this.popupDropboxBreakpoint=t.popupDropboxBreakpoint,this.popupPosition=t.popupPosition,this.onServerSearch=t.onServerSearch,this.labelRenderer=t.labelRenderer,this.initialSelectedValue=0===t.selectedValue?"0":t.selectedValue,this.emptyValue=t.emptyValue,this.ariaLabelledby=t.ariaLabelledby,this.ariaLabelText=t.ariaLabelText,this.ariaLabelClearButtonText=t.ariaLabelClearButtonText,this.maxWidth=t.maxWidth,this.searchDelay=t.searchDelay,this.selectedValues=[],this.selectedOptions=[],this.newValues=[],this.events={},this.tooltipEnterDelay=200,this.searchValue="",this.searchValueOriginal="",this.isAllSelected=!1,(void 0===t.search&&this.multiple||this.allowNewOption||this.showOptionsOnlyOnSearch)&&(this.hasSearch=!0),this.hasServerSearch="function"==typeof this.onServerSearch,(this.maxValues||this.hasServerSearch||this.showOptionsOnlyOnSearch)&&(this.disableSelectAll=!0,this.disableOptionGroupCheckbox=!0),this.keepAlwaysOpen&&(this.dropboxWrapper="self"),this.showAsPopup=this.showDropboxAsPopup&&!this.keepAlwaysOpen&&window.innerWidth<=parseFloat(this.popupDropboxBreakpoint),this.hasSearchContainer=this.hasSearch||this.multiple&&!this.disableSelectAll,this.optionsCount=this.getOptionsCount(t.optionsCount),this.halfOptionsCount=Math.ceil(this.optionsCount/2),this.optionsHeight=this.getOptionsHeight(),this.uniqueId=this.getUniqueId()}},{key:"setDefaultProps",value:function(e){var t={dropboxWrapper:"self",valueKey:"value",labelKey:"label",descriptionKey:"description",aliasKey:"alias",ariaLabelText:"Options list",ariaLabelClearButtonText:"Clear button",optionsCount:5,noOfDisplayValues:50,optionHeight:"40px",noOptionsText:"No options found",noSearchResultsText:"No results found",selectAllText:"Select All",searchNormalize:!1,searchPlaceholderText:"Search...",searchFormLabel:"Search",clearButtonText:"Clear",moreText:"more...",optionsSelectedText:"options selected",optionSelectedText:"option selected",allOptionsSelectedText:"All",placeholder:"Select",position:"bottom left",zIndex:e.keepAlwaysOpen?1:2,tooltipFontSize:"14px",tooltipAlignment:"center",tooltipMaxWidth:"300px",updatePositionThrottle:100,name:"",additionalClasses:"",maxValues:0,showDropboxAsPopup:!0,popupDropboxBreakpoint:"576px",popupPosition:"center",hideValueTooltipOnSelectAll:!0,emptyValue:"",searchDelay:300,focusSelectedOptionOnOpen:!0};return e.hasOptionDescription&&(t.optionsCount=4,t.optionHeight="50px"),Object.assign(t,e)}},{key:"setPropsFromElementAttr",value:function(e){var t=e.ele;Object.keys(x).forEach((function(i){var o=t.getAttribute(i);-1===k.indexOf(i)||""!==o&&"true"!==o||(o=!0),o&&(e[x[i]]=o)}))}},{key:"setEleProps",value:function(){var t=this.$ele;t.virtualSelect=this,t.value=this.multiple?[]:"",t.name=this.name,t.disabled=!1,t.required=this.required,t.autofocus=this.autofocus,t.multiple=this.multiple,t.form=t.closest("form"),t.reset=e.reset,t.setValue=e.setValueMethod,t.setOptions=e.setOptionsMethod,t.setDisabledOptions=e.setDisabledOptionsMethod,t.setEnabledOptions=e.setEnabledOptionsMethod,t.toggleSelectAll=e.toggleSelectAll,t.isAllSelected=e.isAllSelected,t.addOption=e.addOptionMethod,t.getNewValue=e.getNewValueMethod,t.getDisplayValue=e.getDisplayValueMethod,t.getSelectedOptions=e.getSelectedOptionsMethod,t.getDisabledOptions=e.getDisabledOptionsMethod,t.open=e.openMethod,t.close=e.closeMethod,t.focus=e.focusMethod,t.enable=e.enableMethod,t.disable=e.disableMethod,t.destroy=e.destroyMethod,t.validate=e.validateMethod,t.toggleRequired=e.toggleRequiredMethod,this.hasDropboxWrapper&&(this.$dropboxWrapper.virtualSelect=this)}},{key:"setValueMethod",value:function(e,t){var i={},o={},s=[],n=this.multiple,r=e;if(r){if(Array.isArray(r)||(r=[r]),n){var a=this.maxValues;a&&r.length>a&&r.splice(a)}else r.length>1&&(r=[r[0]]);r=r.map((function(e){return e||0===e?e.toString():""})),this.useGroupValue&&(r=this.setGroupOptionsValue(r)),r.forEach((function(e,t){i[e]=!0,o[e]=t})),this.allowNewOption&&r&&this.setNewOptionsFromValue(r)}if(this.options.forEach((function(e){!0!==i[e.value]||e.isDisabled||e.isGroupTitle?e.isSelected=!1:(e.isSelected=!0,s.push(e.value))})),n)this.hasOptionGroup&&this.setGroupsSelectedProp(),s.sort((function(e,t){return o[e]-o[t]}));else{var l=y(s,1);s=l[0]}this.beforeValueSet(),this.setValue(s,{disableEvent:t}),this.afterValueSet()}},{key:"setGroupOptionsValue",value:function(e){var t=[],i={},o={};return e.forEach((function(e){o[e]=!0})),this.options.forEach((function(e){var s=e.value,n=!0===o[s];e.isGroupTitle?n&&(i[e.index]=!0):(n||i[e.groupIndex])&&t.push(s)})),t}},{key:"setGroupsSelectedProp",value:function(){var e=this.isAllGroupOptionsSelected.bind(this);this.options.forEach((function(t){t.isGroupTitle&&(t.isSelected=e(t.index))}))}},{key:"setOptionsMethod",value:function(e,t){this.setOptions(e),this.afterSetOptions(t)}},{key:"setDisabledOptionsMethod",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.setDisabledOptions(e,!0),t||(this.setValueMethod(null),this.toggleAllOptionsClass()),this.setVisibleOptions()}},{key:"setDisabledOptions",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=[];if(e)if(!0===e)t&&this.options.forEach((function(e){return e.isDisabled=!0,i.push(e.value),e}));else{i=e.map((function(e){return e.toString()}));var o={};i.forEach((function(e){o[e]=!0})),t&&this.options.forEach((function(e){return e.isDisabled=!0===o[e.value],e}))}else t&&this.options.forEach((function(e){return e.isDisabled=!1,e}));this.disabledOptions=i}},{key:"setEnabledOptionsMethod",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.setEnabledOptions(e),t||(this.setValueMethod(null),this.toggleAllOptionsClass()),this.setVisibleOptions()}},{key:"setEnabledOptions",value:function(e){if(void 0!==e){var t=[];if(!0===e)this.options.forEach((function(e){return e.isDisabled=!1,e}));else{var i={};e.forEach((function(e){i[e]=!0})),this.options.forEach((function(e){var o=!0!==i[e.value];return e.isDisabled=o,o&&t.push(e.value),e}))}this.disabledOptions=t}}},{key:"setOptions",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],i=[],o=this.disabledOptions.length,n=this.valueKey,r=this.labelKey,a=this.descriptionKey,l=this.aliasKey,u=this.hasOptionDescription,c=s.getString,p=s.convertToBoolean,h=this.secureText.bind(this),d=this.getAlias.bind(this),y=0,b=!1,m={},g=!1;this.disabledOptions.forEach((function(e){m[e]=!0})),Array.isArray(t)&&t.forEach((function t(O){var S;"object"!==f(O)&&(v(S={},n,O),v(S,r,O),O=S);var x=h(c(O[n])),w=h(c(O[r])),k=O.options,E=!!k,C={index:y,value:x,label:w,labelNormalized:e.searchNormalize?s.normalizeString(w).toLowerCase():w.toLowerCase(),alias:d(O[l]),isVisible:p(O.isVisible,!0),isNew:O.isNew||!1,isGroupTitle:E,classNames:O.classNames};if(g||""!==x||(g=!0),o&&(C.isDisabled=!0===m[x]),O.isGroupOption&&(C.isGroupOption=!0,C.groupIndex=O.groupIndex),u&&(C.description=h(c(O[a]))),O.customData&&(C.customData=O.customData),i.push(C),y+=1,E){var $=C.index;b=!0,k.forEach((function(e){e.isGroupOption=!0,e.groupIndex=$,t(e)}))}}));var O=i.length,S=this.$ele;S.options=i,S.length=O,this.options=i,this.visibleOptionsCount=O,this.lastOptionIndex=O-1,this.newValues=[],this.hasOptionGroup=b,this.hasEmptyValueOption=g,this.setSortedOptions()}},{key:"setServerOptions",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[];this.setOptionsMethod(t,!0);var i=this.selectedOptions,o=this.options,s=!1;if(i.length){var n={};s=!0,o.forEach((function(e){n[e.value]=!0})),i.forEach((function(e){!0!==n[e.value]&&(e.isVisible=!1,o.push(e))})),this.setOptionsMethod(o,!0)}this.allowNewOption&&this.searchValue&&(o.some((function(t){return t.label.toLowerCase()===e.searchValue}))||(s=!0,this.setNewOption())),s?(this.setVisibleOptionsCount(),this.multiple&&this.toggleAllOptionsClass(),this.setValueText()):this.updatePosition(),this.setVisibleOptionsCount(),p.removeClass(this.$allWrappers,"server-searching")}},{key:"setSelectedOptions",value:function(){this.selectedOptions=this.options.filter((function(e){return e.isSelected}))}},{key:"setSortedOptions",value:function(){var e=b(this.options);this.showSelectedOptionsFirst&&this.selectedValues.length&&(e=this.hasOptionGroup?this.sortOptionsGroup(e):this.sortOptions(e)),this.sortedOptions=e}},{key:"setVisibleOptions",value:function(){var e=b(this.sortedOptions),t=2*this.optionsCount,i=this.getVisibleStartIndex(),o=this.getNewOption(),s=i+t-1,n=0;o&&(o.visibleIndex=n,n+=1),e=e.filter((function(e){var t=!1;return e.isVisible&&!e.isCurrentNew&&(t=n>=i&&n<=s,e.visibleIndex=n,n+=1),t})),o&&(e=[o].concat(b(e))),this.visibleOptions=e,this.visibleOptionsCount=e.length,this.renderOptions()}},{key:"setOptionsPosition",value:function(e){var t=parseInt((e||this.getVisibleStartIndex())*this.optionHeight);this.$options.style.transform="translate3d(0, ".concat(t,"px, 0)"),p.setData(this.$options,"top",t)}},{key:"setOptionsTooltip",value:function(){var e=this,t=this.getVisibleOptions(),i=this.hasOptionDescription;t.forEach((function(t){var o=e.$dropboxContainer.querySelector('.vscomp-option[data-index="'.concat(t.index,'"]'));p.setData(o.querySelector(".vscomp-option-text"),"tooltip",t.label),i&&p.setData(o.querySelector(".vscomp-option-description"),"tooltip",t.description)}))}},{key:"setValue",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=t.disableEvent,o=void 0!==i&&i,n=t.disableValidation,r=void 0!==n&&n;this.hasEmptyValueOption&&""===e||e?Array.isArray(e)?this.selectedValues=b(e):this.selectedValues=[e]:this.selectedValues=[];var a=this.getValue();this.$ele.value=a,this.$hiddenInput.value=this.getInputValue(a),this.isMaxValuesSelected=!!(this.maxValues&&this.maxValues<=this.selectedValues.length),this.toggleAllOptionsClass(),this.setValueText(),p.toggleClass(this.$allWrappers,"has-value",s.isNotEmpty(this.selectedValues)),p.toggleClass(this.$allWrappers,"max-value-selected",this.isMaxValuesSelected),r||this.validate(),o||p.dispatchEvent(this.$ele,"change",!0)}},{key:"setValueText",value:function(){var e=this,t=this.multiple,i=this.selectedValues,o=this.noOfDisplayValues,n=this.showValueAsTags,r=this.$valueText,a=[],l=[],u=i.length,c=0,h=this.isAllSelected&&!this.hasServerSearch&&!this.disableAllOptionsSelectedText&&!n;if(h&&this.hideValueTooltipOnSelectAll)r.innerHTML="".concat(this.allOptionsSelectedText," (").concat(u,")");else{this.getSelectedOptions({fullDetails:!0,keepSelectionOrder:!0}).some((function(t){if(t.isCurrentNew)return!1;if(c>=o)return!0;var i=t.label;if(a.push(i),c+=1,n){var u=s.willTextOverflow(r.parentElement,i)?e.getTooltipAttrText(i,!1,!0):"",p='\n ').concat(i,'\n \n \n \n ');l.push(p)}else l.push(i);return!1}));var d=u-o;d>0&&l.push('+ '.concat(d," ").concat(this.moreText,""));var v=a.join(", ");if(""===v)r.innerHTML=this.placeholder;else if(r.innerHTML=v,t){var f=this.maxValues;if(this.alwaysShowSelectedOptionsCount||p.hasEllipsis(r)||f||n){var y=''.concat(u,"");if(f&&(y+=' / '.concat(f,"")),h)r.innerHTML="".concat(this.allOptionsSelectedText," (").concat(u,")");else if(n)r.innerHTML=l.join(""),this.$valueTags=r.querySelectorAll(".vscomp-value-tag"),this.setValueTagAttr();else if(!this.alwaysShowSelectedOptionsLabel){var b=1===u?this.optionSelectedText:this.optionsSelectedText;r.innerHTML="".concat(y," ").concat(b)}}else l=[]}}var m="";0===u?m=this.placeholder:n||(m=l.join(", ")),n||p.setData(r,"tooltip",m),t&&(n?this.updatePosition():p.setData(r,"tooltipEllipsisOnly",0===u))}},{key:"setSearchValue",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=arguments.length>2&&void 0!==arguments[2]&&arguments[2];if(e!==this.searchValueOriginal||i){t||(this.$searchInput.value=e);var o=e.replace(/\\/g,"").toLowerCase().trim();this.searchValue=o,this.searchValueOriginal=e,p.toggleClass(this.$allWrappers,"has-search-value",e),this.afterSetSearchValue()}}},{key:"setVisibleOptionsCount",value:function(){var e,t=0,i=!1,o=this.searchGroup,n=this.showOptionsOnlyOnSearch,r=this.searchByStartsWith,a=this.searchValue;a=this.searchNormalize?s.normalizeString(a):a;var l=this.isOptionVisible.bind(this);this.hasOptionGroup&&(e=this.getVisibleOptionGroupsMapping(a)),this.options.forEach((function(s){var u;s.isCurrentNew||(n&&!a?(s.isVisible=!1,u={isVisible:!1,hasExactOption:!1}):u=l({data:s,searchValue:a,hasExactOption:i,visibleOptionGroupsMapping:e,searchGroup:o,searchByStartsWith:r}),u.isVisible&&(t+=1),i||(i=u.hasExactOption))})),this.allowNewOption&&(a&&!i?(this.setNewOption(),t+=1):this.removeNewOption()),this.visibleOptionsCount=t,this.afterSetVisibleOptionsCount()}},{key:"setOptionProp",value:function(e,t,i){this.options[e]&&(this.options[e][t]=i)}},{key:"setOptionsHeight",value:function(){this.$optionsList.style.height="".concat(this.optionHeight*this.visibleOptionsCount,"px")}},{key:"setOptionsContainerHeight",value:function(e){var t;e?this.showAsPopup&&(this.optionsCount=this.getOptionsCount(),this.halfOptionsCount=Math.ceil(this.optionsCount/2),t=this.getOptionsHeight(),this.optionsHeight=t):(t=this.optionsHeight,this.keepAlwaysOpen&&(p.setStyle(this.$noOptions,"height",t),p.setStyle(this.$noSearchResults,"height",t))),p.setStyle(this.$optionsContainer,"max-height",t),this.afterSetOptionsContainerHeight(e)}},{key:"setNewOption",value:function(e){var t=e||this.searchValueOriginal.trim();if(t){var i=this.getNewOption();if(i){var o=i.index;this.setOptionProp(o,"value",this.secureText(t)),this.setOptionProp(o,"label",this.secureText(t))}else{var s={value:t,label:t};e?(s.isNew=!0,this.newValues.push(t)):s.isCurrentNew=!0,this.addOption(s)}}}},{key:"setSelectedProp",value:function(){var e={};this.selectedValues.forEach((function(t){e[t]=!0})),this.options.forEach((function(t){!0===e[t.value]&&(t.isSelected=!0)}))}},{key:"setNewOptionsFromValue",value:function(e){if(e){var t=this.setNewOption.bind(this),i={};this.options.forEach((function(e){i[e.value]=!0})),e.forEach((function(e){e&&!0!==i[e]&&t(e)}))}}},{key:"setDropboxWrapperWidth",value:function(){if(!this.showAsPopup){var e=this.dropboxWidth||"".concat(this.$wrapper.offsetWidth,"px");p.setStyle(this.$dropboxContainer,"max-width",e)}}},{key:"setEleStyles",value:function(){var e=this.maxWidth,t={};e&&(t["max-width"]=e),p.setStyles(this.$ele,t)}},{key:"setDropboxStyles",value:function(){var e=this.dropboxWidth,t={},i={"z-index":this.zIndex};e&&(this.showAsPopup?t["max-width"]=e:i.width=e),p.setStyles(this.$dropboxContainer,i),p.setStyles(this.$dropbox,t)}},{key:"setOptionAttr",value:function(){var e=this.$visibleOptions,t=this.options,i="".concat(this.optionHeight,"px"),o=p.setStyle,s=p.getData,n=p.setData;e&&e.length&&e.forEach((function(e){var r=t[s(e,"index")];o(e,"height",i),n(e,"value",r.value)}))}},{key:"setValueTagAttr",value:function(){var e=this.$valueTags;if(e&&e.length){var t=p.getData,i=p.setData,o=this.options;e.forEach((function(e){var s=t(e,"index");if(void 0!==s){var n=o[s];i(e,"value",n.value)}}))}}},{key:"setScrollTop",value:function(){var e=this.selectedValues;if(!this.showSelectedOptionsFirst&&this.focusSelectedOptionOnOpen&&0!==e.length){var t,i={};e.forEach((function(e){i[e]=!0})),this.options.some((function(e){return!!i[e.value]&&(t=e.visibleIndex,!0)})),t&&(this.$optionsContainer.scrollTop=this.optionHeight*t)}}},{key:"getVisibleOptions",value:function(){return this.visibleOptions||[]}},{key:"getValue",value:function(){return this.multiple?this.useGroupValue?this.getGroupValue():this.selectedValues:this.selectedValues[0]||""}},{key:"getGroupValue",value:function(){var e=[],t={};return this.options.forEach((function(i){if(i.isSelected){var o=i.value;i.isGroupTitle?o&&(t[i.index]=!0,e.push(o)):!0!==t[i.groupIndex]&&e.push(o)}})),e}},{key:"getInputValue",value:function(e){var t=e;return t&&t.length?this.setValueAsArray&&this.multiple&&(t=JSON.stringify(t)):t=this.emptyValue,t}},{key:"getFirstVisibleOptionIndex",value:function(){return Math.ceil(this.$optionsContainer.scrollTop/this.optionHeight)}},{key:"getVisibleStartIndex",value:function(){var e=this.getFirstVisibleOptionIndex()-this.halfOptionsCount;return e<0&&(e=0),e}},{key:"getTooltipAttrText",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],i=arguments.length>2&&void 0!==arguments[2]&&arguments[2],o={"data-tooltip":(s.containsHTML(e)?s.replaceDoubleQuotesWithHTML(e):e)||"","data-tooltip-enter-delay":this.tooltipEnterDelay,"data-tooltip-z-index":this.zIndex,"data-tooltip-font-size":this.tooltipFontSize,"data-tooltip-alignment":this.tooltipAlignment,"data-tooltip-max-width":this.tooltipMaxWidth,"data-tooltip-ellipsis-only":t,"data-tooltip-allow-html":i};return p.getAttributesText(o)}},{key:"getOptionObj",value:function(e){if(e){var t=s.getString,i=this.secureText.bind(this);return{index:e.index,value:i(t(e.value)),label:i(t(e.label)),description:i(t(e.description)),alias:this.getAlias(e.alias),isCurrentNew:e.isCurrentNew||!1,isNew:e.isNew||!1,isVisible:!0}}}},{key:"getNewOption",value:function(){var e=this.options[this.lastOptionIndex];if(e&&e.isCurrentNew)return e}},{key:"getOptionIndex",value:function(e){var t;return this.options.some((function(i){return i.value===e&&(t=i.index,!0)})),t}},{key:"getNewValue",value:function(){var e={};this.newValues.forEach((function(t){e[t]=!0}));var t=this.selectedValues.filter((function(t){return!0===e[t]}));return this.multiple?t:t[0]}},{key:"getAlias",value:function(e){var t=e;return t&&(t=(t=Array.isArray(t)?t.join(","):t.toString().trim()).toLowerCase()),t||""}},{key:"getDisplayValue",value:function(){var e=[];return this.options.forEach((function(t){t.isSelected&&e.push(t.label)})),this.multiple?e:e[0]||""}},{key:"getSelectedOptions",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.fullDetails,i=void 0!==t&&t,o=e.keepSelectionOrder,s=void 0!==o&&o,n=this.valueKey,r=this.labelKey,a=this.selectedValues,l=[];if(this.options.forEach((function(e){if(e.isSelected&&!e.isGroupTitle)if(i)l.push(e);else{var t,o=(v(t={},n,e.value),v(t,r,e.label),t);e.isNew&&(o.isNew=!0),e.customData&&(o.customData=e.customData),l.push(o)}})),s){var u={};a.forEach((function(e,t){u[e]=t})),l.sort((function(e,t){return u[e.value]-u[t.value]}))}return this.multiple||i?l:l[0]}},{key:"getDisabledOptions",value:function(){var e=this.valueKey,t=this.labelKey,i=this.disabledOptions,o={},s=[];return i.forEach((function(e){o[e]=!0})),this.options.forEach((function(i){var n,r=i.value,a=i.label;o[r]&&s.push((v(n={},e,r),v(n,t,a),n))})),s}},{key:"getVisibleOptionGroupsMapping",value:function(e){var t=this.options,i={},o=this.isOptionVisible.bind(this);return(t=this.structureOptionGroup(t)).forEach((function(t){i[t.index]=t.options.some((function(t){return o({data:t,searchValue:e}).isVisible}))})),i}},{key:"getOptionsCount",value:function(e){var t;if(this.showAsPopup){var i=80*window.innerHeight/100-48;this.hasSearchContainer&&(i-=40),t=Math.floor(i/this.optionHeight)}else t=parseInt(e);return t}},{key:"getOptionsHeight",value:function(){return"".concat(this.optionsCount*this.optionHeight,"px")}},{key:"getSibling",value:function(e,t){var i="next"===t?"nextElementSibling":"previousElementSibling",o=e;do{o&&(o=o[i])}while(p.hasClass(o,"disabled")||p.hasClass(o,"group-title"));return o}},{key:"getUniqueId",value:function(){var e=s.getRandomInt(1e4);return document.querySelector("#vscomp-ele-wrapper-".concat(e))?this.getUniqueId():e}},{key:"initDropboxPopover",value:function(){var e={ele:this.$ele,target:this.$dropboxContainer,position:this.position,zIndex:this.zIndex,margin:4,transitionDistance:30,hideArrowIcon:!0,disableManualAction:!0,disableUpdatePosition:!this.hasDropboxWrapper,updatePositionThrottle:this.updatePositionThrottle,afterShow:this.afterShowPopper.bind(this),afterHide:this.afterHidePopper.bind(this)};this.dropboxPopover=new PopoverComponent(e)}},{key:"openDropbox",value:function(e){this.isSilentOpen=e,e?p.setStyle(this.$dropboxContainer,"display","inline-flex"):(p.dispatchEvent(this.$ele,"beforeOpen"),p.setAria(this.$wrapper,"expanded",!0)),this.setDropboxWrapperWidth(),p.removeClass(this.$allWrappers,"closed"),p.changeTabIndex(this.$allWrappers,0),this.dropboxPopover&&!e?this.dropboxPopover.show():this.afterShowPopper()}},{key:"afterShowPopper",value:function(){var e=this.isSilentOpen;this.isSilentOpen=!1,e||(this.moveSelectedOptionsFirst(),this.setScrollTop(),p.addClass(this.$allWrappers,"focused"),this.showAsPopup?(p.addClass(this.$body,"vscomp-popup-active"),this.isPopupActive=!0):this.focusElementOnOpen(),p.dispatchEvent(this.$ele,"afterOpen"))}},{key:"closeDropbox",value:function(e){this.isSilentClose=e,this.keepAlwaysOpen?this.removeOptionFocus():(e?p.setStyle(this.$dropboxContainer,"display",""):(p.dispatchEvent(this.$ele,"beforeClose"),p.setAria(this.$wrapper,"expanded",!1),p.setAria(this.$wrapper,"activedescendant","")),this.dropboxPopover&&!e?this.dropboxPopover.hide():this.afterHidePopper())}},{key:"afterHidePopper",value:function(){var e=this.isSilentClose;this.isSilentClose=!1,p.removeClass(this.$allWrappers,"focused"),this.removeOptionFocus(),!e&&this.isPopupActive&&(p.removeClass(this.$body,"vscomp-popup-active"),this.isPopupActive=!1),p.addClass(this.$allWrappers,"closed"),e||(p.dispatchEvent(this.$ele,"afterClose"),(this.initialSelectedValue&&0===this.initialSelectedValue.length||this.selectedValues.length>0)&&this.focus())}},{key:"moveSelectedOptionsFirst",value:function(){this.showSelectedOptionsFirst&&(this.setSortedOptions(),this.$optionsContainer.scrollTop&&this.selectedValues.length?this.scrollToTop():this.setVisibleOptions())}},{key:"toggleDropbox",value:function(){this.isOpened()?this.closeDropbox():this.openDropbox()}},{key:"updatePosition",value:function(){this.dropboxPopover&&this.isOpened()&&this.$ele.updatePosition()}},{key:"isOpened",value:function(){return!p.hasClass(this.$wrapper,"closed")}},{key:"focusSearchInput",value:function(){var e=this.$searchInput;e&&e.focus()}},{key:"focusElementOnOpen",value:function(){var e=this.$searchInput,t=!this.options.length&&!this.hasServerSearch;if(e)t&&!this.allowNewOption?(p.setAttr(e,"disabled",""),this.$noOptions.focus()):(e.removeAttribute("disabled"),e.focus());else{var i=this.$dropbox.querySelector('[tabindex="0"]');void 0!==p.getData(i,"index")?this.focusOption({direction:"next"}):i?i.focus():this.focusFirstVisibleOption()}}},{key:"focusFirstVisibleOption",value:function(){var e=this.$optionsContainer.querySelector("[data-index='".concat(this.getFirstVisibleOptionIndex(),"']"));e?(p.hasClass(e,"group-title")&&(e=this.getSibling(e,"next")),p.setAttr(e,"tabindex","0"),this.$optionsContainer.scrollTop=this.optionHeight*this.getFirstVisibleOptionIndex(),this.focusOption({focusFirst:!0}),e.focus()):(e=this.$dropbox.querySelector('[tabindex="0"]'))&&e.focus()}},{key:"focusOption",value:function(){var e,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=t.direction,o=t.$option,s=t.focusFirst,n=this.$dropboxContainer.querySelector(".vscomp-option.focused");if(o)e=o;else if(!n||s){var r=this.getFirstVisibleOptionIndex();e=this.$dropboxContainer.querySelector('.vscomp-option[data-visible-index="'.concat(r,'"]')),(p.hasClass(e,"disabled")||p.hasClass(e,"group-title"))&&(e=this.getSibling(e,"next"))}else e=this.getSibling(n,i);e&&e!==n&&(n&&this.toggleOptionFocusedState(n,!1),this.toggleOptionFocusedState(e,!0),this.toggleFocusedProp(p.getData(e,"index"),!0),this.moveFocusedOptionToView(e))}},{key:"moveFocusedOptionToView",value:function(e){var t=e||this.$dropboxContainer.querySelector(".vscomp-option.focused");if(t){var i,o=this.$optionsContainer.getBoundingClientRect(),s=t.getBoundingClientRect(),n=o.top,r=o.bottom,a=o.height,l=s.top,u=s.bottom,c=s.height,h=t.offsetTop,d=p.getData(this.$options,"top","number");n>l?i=h+d:r1&&void 0!==arguments[1]?arguments[1]:{}).event;if(e){var i=!p.hasClass(e,"selected");if(i){if(this.multiple&&this.isMaxValuesSelected)return}else if(!this.multiple)return void this.closeDropbox();var o=this.selectedValues,n=p.getData(e,"value"),r=p.getData(e,"index","number"),a=p.hasClass(e,"current-new"),l=!1,u=this.lastSelectedOptionIndex;if(this.lastSelectedOptionIndex=null,this.toggleSelectedProp(r,i),i){if(this.multiple)o.push(n),this.toggleAllOptionsClass(),this.toggleGroupOptionsParent(e),t&&t.shiftKey&&(l=!0);else{o.length&&this.toggleSelectedProp(this.getOptionIndex(o[0]),!1),o=[n];var c=this.$dropboxContainer.querySelector(".vscomp-option.selected");c&&this.toggleOptionSelectedState(c,!1),this.closeDropbox(),a||this.setSearchValue("")}this.lastSelectedOptionIndex=r,this.toggleOptionSelectedState(e)}else this.multiple&&(this.toggleOptionSelectedState(e),s.removeItemFromArray(o,n),this.toggleAllOptionsClass(!1),this.toggleGroupOptionsParent(e,!1));a&&this.beforeSelectNewValue(),this.setValue(o),l&&this.selectRangeOptions(u,r)}}},{key:"selectFocusedOption",value:function(){this.selectOption(this.$dropboxContainer.querySelector(".vscomp-option.focused"))}},{key:"selectRangeOptions",value:function(e,t){var i=this;if("number"==typeof e&&!this.maxValues){var o,s,n=this.selectedValues,r=this.hasOptionGroup,a={};if(eo&&t0||""===this.searchValue)&&(t=this.isAllOptionsSelected(!0)),p.toggleClass(this.$toggleAllCheckbox,"checked",e||t),this.isAllSelected=e}}},{key:"isAllOptionsSelected",value:function(e){var t=!1;return this.options.length&&this.selectedValues.length&&(t=!this.options.some((function(t){return!t.isSelected&&!t.isDisabled&&!t.isGroupTitle&&(!e||t.isVisible)}))),t}},{key:"isAllGroupOptionsSelected",value:function(e){var t=!1;return this.options.length&&(t=!this.options.some((function(t){return!t.isSelected&&!t.isDisabled&&!t.isGroupTitle&&t.groupIndex===e}))),t}},{key:"toggleGroupOptionsParent",value:function(e,t){if(this.hasOptionGroup&&!this.disableOptionGroupCheckbox&&e){var i=p.getData(e,"groupIndex");void 0!==i&&(i=parseInt(i));var o=this.$options.querySelector('.vscomp-option[data-index="'.concat(i,'"]')),s="boolean"==typeof t?t:this.isAllGroupOptionsSelected(i);this.toggleGroupTitleCheckbox(o,s),this.toggleGroupTitleProp(i,s)}}},{key:"toggleGroupTitleProp",value:function(e,t){var i="boolean"==typeof t?t:this.isAllGroupOptionsSelected(e);this.toggleSelectedProp(e,i)}},{key:"toggleGroupOptions",value:function(e,t){var i=this;if(this.hasOptionGroup&&!this.disableOptionGroupCheckbox&&e){var o=p.getData(e,"index","number"),n=this.selectedValues,r=this.selectAllOnlyVisible,a={},l=s.removeItemFromArray;n.forEach((function(e){a[e]=!0})),this.options.forEach((function(e){if(!e.isDisabled&&e.groupIndex===o){var i=e.value;!t||r&&!e.isVisible?(e.isSelected=!1,a[i]&&l(n,i)):(e.isSelected=!0,a[i]||n.push(i))}})),this.toggleAllOptionsClass(!!t&&null),this.setValue(n),setTimeout((function(){i.renderOptions()}),0)}}},{key:"toggleGroupTitleCheckbox",value:function(e,t){if(e){var i=p.getData(e,"index","number");this.toggleSelectedProp(i,t),this.toggleOptionSelectedState(e,t)}}},{key:"toggleFocusedProp",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.focusedOptionIndex&&this.setOptionProp(this.focusedOptionIndex,"isFocused",!1),this.setOptionProp(e,"isFocused",t),this.focusedOptionIndex=e}},{key:"toggleSelectedProp",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.setOptionProp(e,"isSelected",t)}},{key:"scrollToTop",value:function(){var e=!this.isOpened();e&&this.openDropbox(!0),this.$optionsContainer.scrollTop>0&&(this.$optionsContainer.scrollTop=0),e&&this.closeDropbox(!0)}},{key:"reset",value:function(){var e=arguments.length>0&&void 0!==arguments[0]&&arguments[0],t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.options.forEach((function(e){e.isSelected=!1})),this.beforeValueSet(!0),this.setValue(null,{disableEvent:t,disableValidation:e}),this.afterValueSet(),e&&p.removeClass(this.$allWrappers,"has-error"),p.dispatchEvent(this.$ele,"reset")}},{key:"addOption",value:function(e,t){if(e){this.lastOptionIndex+=1;var i=this.getOptionObj(d(d({},e),{},{index:this.lastOptionIndex}));this.options.push(i),this.sortedOptions.push(i),t&&(this.visibleOptionsCount+=1,this.afterSetOptions())}}},{key:"removeOption",value:function(e){(e||0===e)&&(this.options.splice(e,1),this.lastOptionIndex-=1)}},{key:"removeNewOption",value:function(){var e=this.getNewOption();e&&this.removeOption(e.index)}},{key:"sortOptions",value:function(e){return e.sort((function(e,t){var i=e.isSelected||e.isAnySelected,o=t.isSelected||t.isAnySelected;return i||o?i&&(!o||e.index0&&void 0!==arguments[0]&&arguments[0],t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];this.virtualSelect.reset(e,t)}},{key:"setValueMethod",value:function(){var e;(e=this.virtualSelect).setValueMethod.apply(e,arguments)}},{key:"setOptionsMethod",value:function(){var e;(e=this.virtualSelect).setOptionsMethod.apply(e,arguments)}},{key:"setDisabledOptionsMethod",value:function(){var e;(e=this.virtualSelect).setDisabledOptionsMethod.apply(e,arguments)}},{key:"setEnabledOptionsMethod",value:function(){var e;(e=this.virtualSelect).setEnabledOptionsMethod.apply(e,arguments)}},{key:"toggleSelectAll",value:function(e){this.virtualSelect.toggleAllOptions(e)}},{key:"isAllSelected",value:function(){return this.virtualSelect.isAllSelected}},{key:"addOptionMethod",value:function(e){this.virtualSelect.addOption(e,!0)}},{key:"getNewValueMethod",value:function(){return this.virtualSelect.getNewValue()}},{key:"getDisplayValueMethod",value:function(){return this.virtualSelect.getDisplayValue()}},{key:"getSelectedOptionsMethod",value:function(e){return this.virtualSelect.getSelectedOptions(e)}},{key:"getDisabledOptionsMethod",value:function(){return this.virtualSelect.getDisabledOptions()}},{key:"openMethod",value:function(){return this.virtualSelect.openDropbox()}},{key:"closeMethod",value:function(){return this.virtualSelect.closeDropbox()}},{key:"focusMethod",value:function(){return this.virtualSelect.focus()}},{key:"enableMethod",value:function(){return this.virtualSelect.enable()}},{key:"disableMethod",value:function(){return this.virtualSelect.disable()}},{key:"destroyMethod",value:function(){return this.virtualSelect.destroy()}},{key:"validateMethod",value:function(){return this.virtualSelect.validate()}},{key:"toggleRequiredMethod",value:function(e){return this.virtualSelect.toggleRequired(e)}},{key:"onResizeMethod",value:function(){document.querySelectorAll(".vscomp-ele-wrapper").forEach((function(e){e.parentElement.virtualSelect.onResize()}))}}],i&&O(t.prototype,i),o&&O(t,o),Object.defineProperty(t,"prototype",{writable:!1}),e}();document.addEventListener("reset",$.onFormReset),document.addEventListener("submit",$.onFormSubmit),window.addEventListener("resize",$.onResizeMethod),x=$.getAttrProps(),window.VirtualSelect=$,"undefined"!=typeof NodeList&&NodeList.prototype&&!NodeList.prototype.forEach&&(NodeList.prototype.forEach=Array.prototype.forEach)}(),function(){"use strict";function e(e){return function(e){if(Array.isArray(e))return t(e)}(e)||function(e){if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)}(e)||function(e,i){if(e){if("string"==typeof e)return t(e,i);var o=Object.prototype.toString.call(e).slice(8,-1);return"Object"===o&&e.constructor&&(o=e.constructor.name),"Map"===o||"Set"===o?Array.from(e):"Arguments"===o||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(o)?t(e,i):void 0}}(e)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function t(e,t){(null==t||t>e.length)&&(t=e.length);for(var i=0,o=new Array(t);ii-s-t.width?"left":"right",vertical:n>o-n-t.height?"top":"bottom"}}},{key:"getAbsoluteCoords",value:function(e){if(e){var t=e.getBoundingClientRect(),i=window.pageXOffset,o=window.pageYOffset;return{width:t.width,height:t.height,top:t.top+o,right:t.right+i,bottom:t.bottom+o,left:t.left+i}}}},{key:"getCoords",value:function(e){return e?e.getBoundingClientRect():{}}},{key:"getData",value:function(e,t,i){if(e){var o=e?e.dataset[t]:"";return"number"===i?o=parseFloat(o)||0:"true"===o?o=!0:"false"===o&&(o=!1),o}}},{key:"setData",value:function(e,t,i){e&&(e.dataset[t]=i)}},{key:"setStyle",value:function(e,t,i){e&&(e.style[t]=i)}},{key:"show",value:function(e){var i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"block";t.setStyle(e,"display",i)}},{key:"hide",value:function(e){t.setStyle(e,"display","none")}},{key:"getHideableParent",value:function(e){for(var t,i=e.parentElement;i;){var o=getComputedStyle(i).overflow;if(-1!==o.indexOf("scroll")||-1!==o.indexOf("auto")){t=i;break}i=i.parentElement}return t}}],i&&function(e,t){for(var i=0;ig?g-f:M:k+c>F&&("right"===C?n="left":k=F+fO?O-h:P:E+p>I&&("bottom"===C?n="top":E=I+hc-z&&(W=c-z):"left"!==C&&"right"!==C||((q=b/2+m-R)p-z&&(q=p-z)),i.setStyle(this.$arrowEle,"transform","translate3d(".concat(parseInt(W),"px, ").concat(parseInt(q),"px, 0) ").concat(K))}i.hide(this.$popperEle)}},{key:"resetPosition",value:function(){i.setStyle(this.$popperEle,"transform","none"),this.setPosition()}},{key:"show",value:function(){var e=this,t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o=t.resetPosition,s=t.data;clearTimeout(this.exitDelayTimeout),clearTimeout(this.hideDurationTimeout),o&&this.resetPosition(),this.enterDelayTimeout=setTimeout((function(){var t=i.getData(e.$popperEle,"left"),o=i.getData(e.$popperEle,"top"),n="translate3d(".concat(parseInt(t),"px, ").concat(parseInt(o),"px, 0)"),r=e.showDuration;i.show(e.$popperEle,"inline-flex"),i.getCoords(e.$popperEle),i.setStyle(e.$popperEle,"transitionDuration",r+"ms"),i.setStyle(e.$popperEle,"transform",n),i.setStyle(e.$popperEle,"opacity",1),e.showDurationTimeout=setTimeout((function(){"function"==typeof e.afterShowCallback&&e.afterShowCallback(s)}),r)}),this.enterDelay)}},{key:"hide",value:function(){var e=this,t=(arguments.length>0&&void 0!==arguments[0]?arguments[0]:{}).data;clearTimeout(this.enterDelayTimeout),clearTimeout(this.showDurationTimeout),this.exitDelayTimeout=setTimeout((function(){if(e.$popperEle){var o=parseInt(i.getData(e.$popperEle,"fromLeft")),s=parseInt(i.getData(e.$popperEle,"fromTop")),n="translate3d(".concat(o,"px, ").concat(s,"px, 0)"),r=e.hideDuration;i.setStyle(e.$popperEle,"transitionDuration",r+"ms"),i.setStyle(e.$popperEle,"transform",n),i.setStyle(e.$popperEle,"opacity",0),e.hideDurationTimeout=setTimeout((function(){i.hide(e.$popperEle),"function"==typeof e.afterHideCallback&&e.afterHideCallback(t)}),r)}}),this.exitDelay)}},{key:"updatePosition",value:function(){i.setStyle(this.$popperEle,"transitionDuration","0ms"),this.resetPosition();var e=parseInt(i.getData(this.$popperEle,"left")),t=parseInt(i.getData(this.$popperEle,"top"));i.show(this.$popperEle,"inline-flex"),i.setStyle(this.$popperEle,"transform","translate3d(".concat(e,"px, ").concat(t,"px, 0)"))}}],t&&function(e,t){for(var i=0;i1&&void 0!==arguments[1]&&arguments[1]}},{key:"removeArrayEmpty",value:function(e){return Array.isArray(e)&&e.length?e.filter((function(e){return!!e})):[]}},{key:"throttle",value:function(e,t){var i,o=0;return function(){for(var s=arguments.length,n=new Array(s),r=0;re.length)&&(t=e.length);for(var i=0,o=new Array(t);i'),e=this.$popover.querySelector(".pop-comp-arrow")),this.$arrowEle=e}}},{key:"destroy",value:function(){this.removeEvents()}}])&&p(t.prototype,i),s&&p(t,s),Object.defineProperty(t,"prototype",{writable:!1}),t;var t,i,s}();d=y.getAttrProps(),window.PopoverComponent=y}(); \ No newline at end of file diff --git a/html/monitorJobs.html b/html/monitorJobs.html new file mode 100644 index 0000000..1e050fb --- /dev/null +++ b/html/monitorJobs.html @@ -0,0 +1,126 @@ + + + + CollabCentral + + + + + + + + + +
+
+ +
+
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/html/monitorJobs.js b/html/monitorJobs.js new file mode 100644 index 0000000..93fe635 --- /dev/null +++ b/html/monitorJobs.js @@ -0,0 +1,132 @@ +var pathArray = window.location.pathname.split('/'); +var appName = pathArray[2]; + +if (getCookie("id")) { + console.log("Found ID Cookie: " + getCookie("id")); + + // Fetch bot metadata + authorization state, then render the page. + fetch('/CollabCentral/' + appName + '/info') + .then(res => res.json()) + .then(info => { + document.title = info.label; + document.getElementById("appLabel").innerHTML = info.label; + document.getElementById("name").innerHTML = getCookie("displayName") || ""; + document.getElementById("appName").href = info.faviconUrl; + document.getElementById("jobLink").href = "/CollabCentral/" + appName + "/sendMessage.html"; + + var appIcon = document.getElementById("appIcon"); + appIcon.src = info.iconUrl; + appIcon.style.maxHeight = "150px"; + appIcon.style.maxWidth = "150px"; + + if (!info.authorized) { + document.getElementById("unauthorizedPanel").classList.remove("hidden"); + return; + } + + document.getElementById("jobsPanel").classList.remove("hidden"); + initJobTables(); + }) + .catch(err => console.error("Failed to load /info:", err)); +} else { + console.log("No ID Cookie found."); + + var randomNumber = Math.random().toString(); + randomNumber = randomNumber.substring(2, randomNumber.length); + fetch('/CollabCentral/' + appName + '/authUrl') + .then(res => res.text()) + .then((res) => { + window.location.href = res + randomNumber; + }); +} + +function initJobTables() { + $('#runningJobs').dataTable({ + ajax: { + url: '/CollabCentral/' + appName + '/jobs/list/running', + dataSrc: '' + }, + columns: [ + { data: 'senderDisplayName' }, + { data: 'appName' }, + { data: 'startTime' }, + { data: 'totalRecipients' }, + { + data: 'completedRecipients', + render: function (val, type, row) { + if (type !== 'display' || row.jobId === undefined || row.jobId === null) return val; + return val + ' view'; + } + } + ], + searching: false, + info: false, + ordering: false, + paging: false + }); + + $('#scheduledJobs').dataTable({ + ajax: { + url: '/CollabCentral/' + appName + '/jobs/list/scheduled', + dataSrc: '' + }, + columns: [ + { data: 'senderDisplayName' }, + { data: 'appName' }, + { data: 'message' }, + { data: 'totalRecipients' }, + { data: 'scheduledFor' } + ], + searching: false, + info: false, + ordering: false, + paging: false + }); + + $('#completedJobs').dataTable({ + ajax: { + url: '/CollabCentral/' + appName + '/jobs/list/completed', + dataSrc: '' + }, + columns: [ + { + data: 'jobId', + render: function (jobId) { + if (jobId === undefined || jobId === null) return ''; + return '#' + jobId + ''; + } + }, + { data: 'senderDisplayName' }, + { data: 'appName' }, + { data: 'message.english.html' }, + { data: 'startTime' }, + { data: 'endTime' }, + { data: 'stats.totalTime' }, + { data: 'stats.webexTime' }, + { data: 'stats.averageTime' }, + { data: 'stats.succeededMsgs' }, + { data: 'stats.totalMsgs' }, + { data: 'stats.succeededMsgPct' } + ], + searching: false, + info: false, + ordering: false, + paging: false + }); +} + +function getCookie(cname) { + let name = cname + "="; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} diff --git a/html/novi.ico b/html/novi.ico new file mode 100644 index 0000000..0306395 Binary files /dev/null and b/html/novi.ico differ diff --git a/html/novi.png b/html/novi.png new file mode 100644 index 0000000..0306395 Binary files /dev/null and b/html/novi.png differ diff --git a/html/sendMessage.html b/html/sendMessage.html new file mode 100644 index 0000000..96bde4b --- /dev/null +++ b/html/sendMessage.html @@ -0,0 +1,308 @@ + + + + CollabCentral + + + + + + + + + + + + + + + +
+
+ +
+
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/html/sendMessage.js b/html/sendMessage.js new file mode 100644 index 0000000..de1e1cd --- /dev/null +++ b/html/sendMessage.js @@ -0,0 +1,222 @@ +const listbox = document.querySelector('#list'); +const modal = document.querySelector(".modal"); +const overlay = document.querySelector(".overlay"); +const openModalBtn = document.querySelector(".btn-open"); +const closeModalBtn = document.querySelector(".btn-close"); + +var pathArray = window.location.pathname.split('/'); //Gets the path of the URL called +var appName = pathArray[2]; //Gets the appName from the path. + +document.getElementById('sendMessage').onsubmit = function (event) { + + event.preventDefault() // prevent form from posting without JS + var xhttp = new XMLHttpRequest(); // create new AJAX request + + + xhttp.onreadystatechange = function () { + if (this.readyState == this.DONE && this.status == 200) { // sucess from server + result = JSON.parse(xhttp.response); + console.log(xhttp.responseText) + console.log(xhttp.response); + var message = "I sent you a test message in your Webex Client for review.

"; + message += "Recipients: " + result.memberList.length + "

"; + if (document.getElementById("scheduledFor").value) { + message += "Scheduled for " + document.getElementById("scheduledFor").value; + } else { + message += "Message will be sent immediately."; + } + + + document.getElementById("modalMessage").innerHTML = message; + openModal(); + } else { // errors occured + + } + } + + var formData = new FormData() + formData.append('uploadImage', document.getElementById('uploadImage').files[0]) // since inputs allow multi files submission, therefore files are in array + formData.append('message', message.value()) + formData.append('uploadCSV', document.getElementById('uploadCSV').files[0]) // since inputs allow multi files submission, therefore files are in array + formData.append('appName', appName); + + var selectedGroups = []; + for (var group of document.querySelector('#favGroups').getSelectedOptions()) { + selectedGroups.push(group.value); + } + for (var group of document.querySelector('#newGroups').getSelectedOptions()) { + selectedGroups.push(group.value); + } + + formData.append('groups', selectedGroups); + formData.append('scheduledFor', document.getElementById("scheduledFor").value); + xhttp.open("POST", "/CollabCentral/" + appName + "/jobs/edit") + console.log(xhttp); + xhttp.send(formData) +} + +function showImagePreview(event) { + if (event.target.files.length > 0) { + var src = URL.createObjectURL(event.target.files[0]); + var preview = document.getElementById("file-ip-1-preview"); + preview.src = src; + preview.style.display = "block"; + preview.style.maxHeight = "350px"; + preview.style.maxWidth = "500px"; + } +} + + +let id = getCookie("id"); + +// Fetch bot metadata + authorization state, then either render the form or +// show the "not authorized" panel. All bot-specific labels and icons come from +// /info so adding a new bot doesn't require any front-end changes. +fetch('/CollabCentral/' + appName + '/info') + .then(res => res.json()) + .then(info => { + document.title = info.label; + document.getElementById("appLabel").innerHTML = info.label; + document.getElementById("name").innerHTML = getCookie("displayName") || ""; + document.getElementById("appName").href = info.faviconUrl; + document.getElementById("jobLink").href = "/CollabCentral/" + appName + "/monitorJobs.html"; + + var appIcon = document.getElementById("appIcon"); + appIcon.src = info.iconUrl; + appIcon.style.maxHeight = "150px"; + appIcon.style.maxWidth = "150px"; + + if (!info.authorized) { + document.getElementById("unauthorizedPanel").classList.remove("hidden"); + return; + } + + document.getElementById("formPanel").classList.remove("hidden"); + loadFavoriteGroups(); + loadNewGroups(); + }) + .catch(err => { + console.error("Failed to load /info:", err); + }); + +function loadFavoriteGroups() { + var x = document.getElementById("favGroups"); + fetch('/CollabCentral/' + appName + '/user/groups/list') + .then(res => res.json()) + .then((res) => { + for (var group of res) { + var option = document.createElement("option"); + option.text = group.name; + option.value = group.id; + x.add(option); + } + VirtualSelect.init({ ele: '#favGroups' }); + }); +} + +function loadNewGroups() { + var y = document.getElementById("newGroups"); + fetch('/CollabCentral/' + appName + '/user/groups/find') + .then(res => res.json()) + .then((res) => { + for (var group of res) { + var option = document.createElement("option"); + option.text = group.displayName; + option.value = group.id; + y.add(option); + } + VirtualSelect.init({ ele: '#newGroups' }); + }); +} + +function getCookie(cname) { + let name = cname + "="; + let decodedCookie = decodeURIComponent(document.cookie); + let ca = decodedCookie.split(';'); + for (let i = 0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0) == ' ') { + c = c.substring(1); + } + if (c.indexOf(name) == 0) { + return c.substring(name.length, c.length); + } + } + return ""; +} + +const message = new EasyMDE({ + element: document.getElementById('my-text-area'), + toolbar: ["bold", "italic", "|", "heading-1", "heading-2", "heading-3", "|", "code", "unordered-list", "ordered-list", "link", "quote", "|", "preview"], + maxHeight: "200px" +}); + + + +function submit() { + if (document.getElementById("scheduledFor").value) { + fetch("/CollabCentral/" + appName + "/jobs/schedule", { + method: "POST", + redirect: "follow" + }) + .then(response => { + clearForm(); + // HTTP 301 response + if (response.redirected) { + window.location.href = response.url; + } + }) + .catch(function(err) { + console.info(err + " url: " + url); + }); + + } else { + console.log("RunNow selected."); + fetch("/CollabCentral/" + appName + "/jobs/runNow", { + method: "POST", + redirect: "follow" + }) + .then(response => { + clearForm(); + // HTTP 301 response + if (response.redirected) { + window.location.href = response.url; + } + }) + .catch(function(err) { + console.info(err); + }); + } + + + + + closeModal(); +} + +function continueEditing() { + closeModal(); +} + +const openModal = function () { + modal.classList.remove("hidden"); + overlay.classList.remove("hidden"); + }; + + const closeModal = function () { + modal.classList.add("hidden"); + overlay.classList.add("hidden"); + }; + + overlay.addEventListener("click", closeModal); + + function clearForm() { + document.getElementById('uploadImage').innerHTML = ""; + message.value(""); + document.getElementById('uploadCSV').innerHTML = ""; + document.getElementById('favGroups').selected = null; + document.getElementById('newGroups').selected = null; + document.getElementById('scheduledFor').innerHTML = ""; + } + + \ No newline at end of file diff --git a/html/techupdates.ico b/html/techupdates.ico new file mode 100644 index 0000000..8abe6fa Binary files /dev/null and b/html/techupdates.ico differ diff --git a/html/techupdates.png b/html/techupdates.png new file mode 100644 index 0000000..0c6178a Binary files /dev/null and b/html/techupdates.png differ diff --git a/index.js b/index.js new file mode 100644 index 0000000..f41e24e --- /dev/null +++ b/index.js @@ -0,0 +1,1346 @@ +#!/usr/bin/env node + +import fs from 'fs'; +import fetch from 'node-fetch'; +import { Headers } from 'node-fetch'; +import express from 'express'; +import bodyParser from 'body-parser'; +import multer from 'multer'; +import sharp from 'sharp'; +import { v4 as uuid } from 'uuid'; +import cookieParser from 'cookie-parser'; +import FormData from 'form-data'; +import cron from "node-cron"; +import PQueue from 'p-queue'; +//import pLimit from "p-limit"; +//const limit = pLimit(10); +const queue = new PQueue({ concurrency: 10 }); + +//Load the config files from storage. +var config = JSON.parse(fs.readFileSync('./config/config.json')); +var jobs = JSON.parse(fs.readFileSync('./config/jobs.json')); +var userPrefs = JSON.parse(fs.readFileSync('./config/userPrefs.json')); + +// Defensive init so code below can safely `.push`, `.filter`, and index into +// these regardless of what the on-disk jobs.json happens to contain. +jobs.building = jobs.building || {}; +jobs.running = Array.isArray(jobs.running) ? jobs.running : []; +jobs.scheduled = Array.isArray(jobs.scheduled) ? jobs.scheduled : []; +jobs.completed = Array.isArray(jobs.completed) ? jobs.completed : []; +if (typeof jobs.lastJobNumber !== 'number') jobs.lastJobNumber = 0; + +// Per-bot tokens live in their own gitignored file so adding a new bot is one +// JSON entry + an env-free restart. Shape: +// { "": { "token": "...", "enabled": true } } +var botTokens = loadBotTokens(); + +function loadBotTokens() { + try { + return JSON.parse(fs.readFileSync('./config/botTokens.json')); + } catch (err) { + console.log('loadBotTokens failed: ' + err.message); + return {}; + } +} + +function getBotToken(appName) { + var entry = botTokens[appName]; + if (!entry) return null; + if (typeof entry === 'string') return entry; // tolerate flat shape + if (entry.enabled === false) return null; + return entry.token || null; +} + +function isBotEnabled(appName) { + return getBotToken(appName) !== null; +} + +// Returns the bot's config block if the bot is both defined in config.json AND +// has an enabled token entry. Returns null otherwise. Use this everywhere +// instead of reaching into config.webex.bot[...] directly so unknown or +// disabled bots can't crash the request. +function getBotConfig(appName) { + if (!appName) return null; + var cfg = config.webex && config.webex.bot && config.webex.bot[appName]; + if (!cfg) return null; + if (!isBotEnabled(appName)) return null; + return cfg; +} + +// Cached bot profiles populated from Webex /people/me at startup so the +// front-end and the completion card can use the bot's actual displayName and +// avatar without hardcoding either in config.json. +// botProfiles[appName] = { displayName, avatar } +var botProfiles = {}; + +async function loadBotProfile(appName) { + var token = getBotToken(appName); + if (!token) return; + try { + var response = await fetchWithRateLimit('https://webexapis.com/v1/people/me', { + method: 'GET', + headers: { 'Authorization': 'Bearer ' + token } + }); + if (!response.ok) { + logger('loadBotProfile', appName + ' /people/me failed: ' + response.status + ' ' + response.statusText); + return; + } + var data = await response.json(); + botProfiles[appName] = { + displayName: data.displayName, + avatar: data.avatar, + personId: data.id, + emails: data.emails + }; + logger('loadBotProfile', appName + ' = ' + data.displayName); + } catch (err) { + logger('loadBotProfile', appName + ' error: ' + (err && err.message || err)); + } +} + +function loadBotProfiles() { + var apps = Object.keys((config.webex && config.webex.bot) || {}); + return Promise.allSettled(apps.filter(isBotEnabled).map(loadBotProfile)); +} + +function getBotProfile(appName) { + return botProfiles[appName] || null; +} + +// Express middleware: 404s any /CollabCentral/:app/* request whose `:app` is +// not a known + enabled bot. Attaches the bot config to req.botConfig for +// downstream handlers. +function requireBot(req, res, next) { + var botCfg = getBotConfig(req.params.app); + if (!botCfg) { + logger("requireBot", "Unknown or disabled bot: " + req.params.app); + return res.status(404).send("Unknown bot: " + req.params.app); + } + req.botConfig = botCfg; + next(); +} + +// Building (draft) jobs are keyed by cookieId AND appName so a user who is +// authorized for more than one bot can have one independent draft per bot. +function buildingKey(req) { + return (req.cookies && req.cookies.id) + ':' + req.params.app; +} + +// Filters one of the global job arrays (running/scheduled/completed) down to +// the jobs that belong to the requested bot. +function jobsForApp(arr, appName) { + return (arr || []).filter(function (j) { return j && j.appName === appName; }); +} + +// Service-account OAuth tokens are rewritten in place by refreshToken(), so +// they live in their own file rather than mixed into config.json. +var serviceAccountToken = loadServiceAccountToken(); + +function loadServiceAccountToken() { + try { + return JSON.parse(fs.readFileSync('./config/token.json')); + } catch (err) { + console.log('loadServiceAccountToken failed: ' + err.message); + return {}; + } +} + +function saveServiceAccountToken(tokenObj) { + fs.writeFileSync('./config/token.json', JSON.stringify(tokenObj, null, 4)); + serviceAccountToken = tokenObj; +} + +function getServiceAccountAccessToken() { + return serviceAccountToken && serviceAccountToken.access_token; +} + +// Builds the Webex OAuth authorize URL for a given bot. The redirect_uri must +// match one of the URIs registered with the Webex integration in the Webex +// developer portal (one URI per bot). Returns null if required env vars are +// missing so callers can return an actionable error instead of a malformed URL. +function buildAuthUrl(appName) { + var clientId = process.env.WEBEX_INTEGRATION_CLIENT_ID; + var template = process.env.OAUTH_CALLBACK_URL_TEMPLATE; + if (!clientId || !template) { + logger('buildAuthUrl', 'Missing WEBEX_INTEGRATION_CLIENT_ID or OAUTH_CALLBACK_URL_TEMPLATE env var.'); + return null; + } + var params = new URLSearchParams(); + params.append('client_id', clientId); + params.append('response_type', 'code'); + params.append('redirect_uri', template.replace(':app', appName)); + params.append('scope', 'spark:kms spark:people_read'); + params.append('state', ''); + return 'https://webexapis.com/v1/authorize?' + params.toString(); +} + +function getOAuthRedirectUri(appName) { + var template = process.env.OAUTH_CALLBACK_URL_TEMPLATE || ''; + return template.replace(':app', appName); +} + +//Load Express Server +var app = express(); +app.use(bodyParser.json()); +app.use(cookieParser()); + +var serverPort = process.env.SERVER_PORT || config.server.port; +var server = app.listen(serverPort, function () { + logger("startup", config.server.name + " running on port " + serverPort + "."); + loadBotProfiles() + .then(() => logger("startup", "Loaded " + Object.keys(botProfiles).length + " bot profile(s).")) + .catch(err => logger("startup", "loadBotProfiles error: " + (err && err.message || err))); +}); +server.setTimeout(3000000); +const upload = multer({ limits: { fileSize: 4000000 } }).fields( + [ + { name: 'uploadImage' }, + { name: 'uploadCSV' } + ] +); + +cron.schedule('0 10 1 * * *', async function () { + var jobFile = JSON.parse(fs.readFileSync('./config/jobs.json')); + var cleanJobs = await cleanCompletedJobs(jobFile); + saveConfig(cleanJobs, './config/jobs.json'); + jobs = JSON.parse(fs.readFileSync('./config/jobs.json')); + +}) + +cron.schedule('0 * * * * *', () => { + var startRefreshTime = new Date(); + var renewBy = serviceAccountToken && serviceAccountToken.renewBy; + if (renewBy && new Date(new Date(renewBy) - 7200000) < new Date()) { + logger("refreshToken", "Renew by: " + new Date(renewBy).toLocaleString()) + logger("refreshToken", "Renew after: " + new Date(new Date(renewBy) - 7200000).toLocaleString()); + logger("refreshToken", "Refreshing token.") + refreshToken() + .then(response => { + logger("tokenRefresh", response); + }) + .catch(error => console.log("refreshToken: " + error)); + } + checkScheduledJobs() + .then((result) => { + console.log(result) + }) + .catch(error => logIt("checkScheduledJobs: " + error)); +}) + +//Routes to be used +// Validate the :app segment for every /CollabCentral/:app/* request. This +// runs before the static mount and all the per-route handlers below, so any +// unknown or disabled bot gets a clean 404 instead of crashing on undefined. +app.use('/CollabCentral/:app', requireBot) +app.use('/CollabCentral/:app', express.static('html')) + +app.get('/status', function (req, res) { + res.status(200).send({ "status": "I'm alive!'" }); +}); + +app.get('/CollabCentral/:app/authUrl', function (req, res) { + var url = buildAuthUrl(req.params.app); + if (!url) { + return res.status(500).send('OAuth is not configured. Check WEBEX_INTEGRATION_CLIENT_ID and OAUTH_CALLBACK_URL_TEMPLATE.'); + } + res.send(url); +}); + +// Returns the bot's display metadata + whether the current cookie is authorized. +// The front-end uses this to drive page labels, icons, and the +// "you don't have access" view, so adding a new bot doesn't require any code +// changes in the HTML/JS. +app.get('/CollabCentral/:app/info', function (req, res) { + var appName = req.params.app; + var botCfg = req.botConfig; // populated by requireBot + var profile = getBotProfile(appName) || {}; + var iconBase = botCfg.iconBase || appName; + var personId = req.cookies && req.cookies.id; + res.status(200).send({ + appName: appName, + label: botCfg.label || appName, + displayName: profile.displayName || botCfg.label || appName, + iconUrl: '/CollabCentral/' + appName + '/' + iconBase + '.png', + faviconUrl: '/CollabCentral/' + appName + '/' + iconBase + '.ico', + avatarUrl: profile.avatar || null, + authorized: isAuthorized(appName, personId) + }); +}); + +app.post('/CollabCentral/:app/jobs/:action', (req, res) => { + if (isAuthorized(req.params.app, req.cookies.id)) { + req.setTimeout(3000000); + if (req.params.action == "edit") { + logger("apiEndpoint(" + req.params.app + ")", "POST /jobs/edit"); + buildJob(req, res) + .then(job => { + sendDirectMessage(job.senderId, job.message.raw, job.imageName, req.params.app) + .then(async function (result) { + jobs.building[buildingKey(req)].message.english = { + "text": result.text, + "markdown": result.markdown, + "html": result.html + } + + await buildTranslations(jobs.building[buildingKey(req)].message.english) + .then(result => { + jobs.building[buildingKey(req)].message = result; + }) + .catch(error => console.log("Error translating: " + error)); + + res.status(200).send(job) + saveConfig(jobs, "./config/jobs.json") + }) + .catch(error => console.log("Error sending message: " + error)); + }) + .catch(error => console.log("Error buildJob: " + error)); + } else if (req.params.action == "runNow") { + logger("apiEndpoint(" + req.params.app + ")", "POST /jobs/runNow"); + jobs.running.push(jobs.building[buildingKey(req)]); + delete jobs.building[buildingKey(req)]; + processRunningQueue() + .catch(error => console.log("sendRunningMessages error: " + error)) + res.status(204).redirect("/CollabCentral/" + req.params.app + "/monitorJobs.html"); + } else if (req.params.action == "schedule") { + logger("apiEndpoint(" + req.params.app + ")", "POST /jobs/schedule"); + jobs.scheduled.push(jobs.building[buildingKey(req)]); + delete jobs.building[buildingKey(req)]; + saveConfig(jobs, './config/jobs.json') + res.status(204).redirect("/CollabCentral/" + req.params.app + "/monitorJobs.html"); + } else { res.status(404) } + } else { res.status(401).send("You are not authorized.") } + +}) + +app.get('/CollabCentral/:app/jobs/list/:scope', (req, res) => { + if (!req.cookies || !isAuthorized(req.params.app, req.cookies.id)) { + return res.status(401).send("You are not authorized."); + } + var appName = req.params.app; + + if (req.params.scope == "all") { + logger("apiEndpoint(" + appName + ")", "GET /jobs/list/all"); + return res.status(200).send({ + building: jobs.building[buildingKey(req)], + running: jobsForApp(jobs.running, appName), + scheduled: jobsForApp(jobs.scheduled, appName), + completed: jobsForApp(jobs.completed, appName) + }); + } + + if (req.params.scope == "building") { + logger("apiEndpoint(" + appName + ")", "GET /jobs/list/building"); + return res.status(200).send(jobs.building[buildingKey(req)]); + } + + if (req.params.scope == "running") { + logger("apiEndpoint(" + appName + ")", "GET /jobs/list/running"); + var runningJobs = []; + for (var job of jobsForApp(jobs.running, appName)) { + var completedSends = 0; + for (var member of (job.memberList || [])) { + if (member.results) completedSends++; + } + runningJobs.push({ + jobId: job.jobId, + senderDisplayName: job.senderDisplayName, + appName: job.appName, + startTime: job.startTime, + totalRecipients: (job.memberList || []).length, + completedRecipients: completedSends + }); + } + return res.status(200).send(runningJobs); + } + + if (req.params.scope == "scheduled") { + logger("apiEndpoint(" + appName + ")", "GET /jobs/list/scheduled"); + var scheduledJobs = []; + for (var job of jobsForApp(jobs.scheduled, appName)) { + scheduledJobs.push({ + senderDisplayName: job.senderDisplayName, + appName: job.appName, + message: job.message && job.message.english && job.message.english.html, + totalRecipients: (job.memberList || []).length, + scheduledFor: job.scheduledFor + }); + } + return res.status(200).send(scheduledJobs); + } + + if (req.params.scope == "completed") { + logger("apiEndpoint(" + appName + ")", "GET /jobs/list/completed"); + return res.status(200).send(jobsForApp(jobs.completed, appName)); + } + + return res.status(404).send(); +}); + +// Returns the full job object for a given jobId, but only if the job belongs +// to the requested bot. Looks across completed/running/scheduled so the same +// URL keeps working as a job moves through its lifecycle. +app.get('/CollabCentral/:app/jobs/detail/:jobId', (req, res) => { + if (!req.cookies || !isAuthorized(req.params.app, req.cookies.id)) { + return res.status(401).send("You are not authorized."); + } + var appName = req.params.app; + var jobId = req.params.jobId; + logger("apiEndpoint(" + appName + ")", "GET /jobs/detail/" + jobId); + + var all = (jobs.completed || []).concat(jobs.running || [], jobs.scheduled || []); + var match = all.find(function (j) { + return j && j.appName === appName && String(j.jobId) === String(jobId); + }); + if (!match) return res.status(404).send("Job not found."); + res.status(200).send(match); +}); + +app.get('/CollabCentral/:app/user/:scope/:action', (req, res) => { + console.log("Got /user/:scope/:action request."); + //console.log(req.cookies); + if (isAuthorized(req.params.app, req.cookies.id)) { + //console.log("Person is authorized for the request.") + if (req.params.scope == "groups") { + if (req.params.action == "list") { + logger("apiEndpoint(" + req.params.app + ")", "GET /user/groups/list"); + res.status(200).send(config.webex.bot[req.params.app].authorized[req.cookies.id].groups) + } else if (req.params.action == "find") { + logger("apiEndpoint(" + req.params.app + ")", "GET /user/groups/find"); + + findWebexGroup() + .then(response => { + //console.log(response) + saveConfig(response, "./testData.json") + + res.status(200).send(response); + }) + + } + } else { res.status(404) } + } else { res.status(401) } +}) + +// Standard options for the session cookies set after a successful OAuth round-trip. +const SESSION_COOKIE_OPTIONS = { + httpOnly: true, + secure: true, + sameSite: 'strict', + maxAge: 24 * 60 * 60 * 1000 +}; + +app.get(`/CollabCentral/:app/oauth`, async function (req, res) { + var appName = req.params.app; + var authCode = req.query.code; + + if (!process.env.WEBEX_INTEGRATION_CLIENT_ID || !process.env.WEBEX_INTEGRATION_CLIENT_SECRET || !process.env.OAUTH_CALLBACK_URL_TEMPLATE) { + logger("oauth", "Missing Webex integration env vars; cannot complete OAuth."); + return res.status(500).send("OAuth is not configured on the server."); + } + if (!authCode) { + return res.status(400).send("Missing OAuth `code` query parameter."); + } + + var url = new URL("https://webexapis.com/v1/access_token"); + var params = new URLSearchParams(); + params.append('grant_type', 'authorization_code'); + params.append('client_id', process.env.WEBEX_INTEGRATION_CLIENT_ID); + params.append('client_secret', process.env.WEBEX_INTEGRATION_CLIENT_SECRET); + params.append('code', authCode); + params.append('redirect_uri', getOAuthRedirectUri(appName)); + + var response; + try { + response = await fetchWithRateLimit(url, { method: 'POST', body: params }); + } catch (error) { + logger("oauth", "Token exchange fetch failed: " + (error && error.message || error)); + return res.status(502).send("Failed to reach Webex to complete OAuth."); + } + + if (!response.ok) { + var errText = await response.text().catch(() => ''); + logger("oauth", "Token exchange returned " + response.status + " " + response.statusText + " " + errText); + return res.status(401).send(response.statusText || "OAuth token exchange failed."); + } + + var jsonData = await response.json(); + var whoami; + try { + whoami = await whoAmI(jsonData.access_token); + } catch (err) { + logger("oauth", "whoAmI failed: " + (err && err.message || err)); + return res.status(502).send("Failed to read your Webex profile after OAuth."); + } + logger("oauth", whoami.displayName + " successfully authed for " + appName + "."); + + res + .cookie('displayName', whoami.displayName, SESSION_COOKIE_OPTIONS) + .cookie('id', whoami.id, SESSION_COOKIE_OPTIONS) + .cookie('avatar', whoami.avatar, SESSION_COOKIE_OPTIONS) + .cookie('email', whoami.userName, SESSION_COOKIE_OPTIONS) + .cookie('orgId', whoami.orgId, SESSION_COOKIE_OPTIONS) + .cookie('access_token', jsonData.access_token, SESSION_COOKIE_OPTIONS) + .cookie('refresh_token', jsonData.refresh_token, SESSION_COOKIE_OPTIONS) + .redirect(301, '/CollabCentral/' + appName + '/sendMessage.html'); +}); + +function buildJob(req, res) { + return new Promise(async function (resolve, reject) { + res.setTimeout(3000000) + var memberList = []; + var memberListPromises = []; + + upload(req, res, async function (err) { + //console.log(req.files); + //console.log(req.body); + jobs.building[buildingKey(req)] = { + "appName": req.body.appName, + "senderId": req.cookies.id, + "senderDisplayName": req.cookies.displayName, + "groups": req.body.groups, + "message": { + "raw": req.body.message, + "english": {} + }, + "memberList": [] + } + // check for error thrown by multer- file size etc + + if (err || req.files === undefined) { + //no file + } else { + if (req.files["uploadImage"]) { + for (var file of req.files["uploadImage"]) { + + let fileName = uuid() + ".jpeg" + var image = await sharp(file.buffer).jpeg({ quality: 40, }).toFile('./uploads/' + fileName).catch(err => { console.log('error: ', err) }) + jobs.building[buildingKey(req)].imageName = fileName; + logger("buildJob", "uploadImage " + file.originalname + " to " + fileName) + } + } + + if (req.files["uploadCSV"]) { + for (var file of req.files["uploadCSV"]) { + let fileName = uuid() + ".csv"; + fs.writeFileSync('./uploads/' + fileName, file.buffer) + jobs.building[buildingKey(req)].csv = fileName; + logger("buildJob", "uploadCSV " + file.originalname + " to " + fileName) + memberListPromises.push(buildPeopleList(fileName)); + } + } + } + + if (req.body.groups) { + logger("buildJob", "Locating groups: " + req.body.groups); + memberListPromises.push(collectGroupMembers(req.body.groups)); + } + + if (req.body.scheduledFor) { + logger("buildJob", "Scheduling for " + new Date(req.body.scheduledFor).toLocaleString()) + jobs.building[buildingKey(req)].scheduledFor = req.body.scheduledFor; + saveConfig(jobs, "./config/jobs.json") + } + + Promise.allSettled(memberListPromises) + .then(peoplePromises => { + console.log(JSON.stringify(peoplePromises)) + for (var peopleList of peoplePromises) { + if (peopleList.status == "fulfilled") { + for (var person of peopleList.value) { + memberList.push(person); + } + } + } + //Make user members unique + var uniqueMembers = [...new Map(memberList.map((m) => [m.id, m])).values()]; + jobs.building[buildingKey(req)].memberList = uniqueMembers; + var numSending = uniqueMembers.length.toString(); + saveConfig(jobs, "./config/jobs.json") + resolve(jobs.building[buildingKey(req)]); + }) + }) + + }) +} + +function collectGroupMembers(groupList) { + return new Promise(async function (resolve, reject) { + + var groups = groupList.split(","); + var memberList = []; + + for (var group of groups) { + try { + var memberCnt = 0; + var groupCnt = 0; + var groupData = await getGroupMembers(group); + + for (var member of groupData.members) { + memberList.push(member); + memberCnt++; + } + if (groupData.groups.length > 0) { + for (var subGroup of groupData.groups) { + logger("collectGroupMembers", "Found new group: " + subGroup.name); + groupCnt++; + groups.push(subGroup.id); + } + } + logger("collectGroupMembers", "Found " + groupData.displayName + " Members: " + memberCnt) + } catch (error) { + console.log("Error groupData: " + error) + } + } + + //console.log("Groups: " + groupList); + var uniqueMembers = [...new Map(memberList.map((m) => [m.id, m])).values()]; + resolve(uniqueMembers); + }) +} + +function getGroupMembers(groupId) { + return new Promise(async function (resolve, reject) { + var groupMembers = { "members": [], "groups": [] }; + var startIndex = 1; + var count = 500; + var memberSize = 10000; + + var requestOptions = { + method: 'GET', + headers: { + 'Authorization': `Bearer ` + getServiceAccountAccessToken(), + 'Content-Type': `application/json` + } + }; + + while (memberSize > (groupMembers.members.length + groupMembers.groups.length)) { + var findGroupByIdUrl = 'https://webexapis.com/v1/groups/' + groupId + '/members?startIndex=' + startIndex + '&count=' + count; + try { + var groupResponse = await fetchWithRateLimit(findGroupByIdUrl, requestOptions); + if (groupResponse.ok) { + var groupData = await groupResponse.json(); + groupMembers.displayName = groupData.displayName; + memberSize = groupData.memberSize; + for (var member of groupData.members) { + if (member.type == "group") { + + groupMembers.groups.push({ "id": member.id, "name": member.displayName }) + } else { + groupMembers.members.push(member) + } + } + startIndex = startIndex + count; + } else { + memberSize = 0; + reject(groupResponse.status + ": " + groupResponse.statusText); + } + } catch (error) { + console.log("Error findGroupById: " + error); + } + } + resolve(groupMembers); + }) +} + +function findWebexGroup() { + return new Promise(async function (resolve, reject) { + + var groups = []; + var startIndex = 1; + var count = 500; + var groupSize = 10000; + + + var url = new URL("https://webexapis.com/v1/groups") + //var params = new URLSearchParams(); + //url.searchParams.append("filter", "displayName contains " + searchTerm); + url.searchParams.append("attributes", "displayName"); + url.searchParams.append("sortBy", "displayName"); + url.searchParams.append("sortOrder", "ascending"); + url.searchParams.append("includeMembers", "false"); + url.searchParams.append("startIndex", startIndex); + url.searchParams.append("count", count); + //console.log(params.toString()) + + var requestOptions = { + method: 'GET', + headers: { + 'Authorization': `Bearer ` + getServiceAccountAccessToken(), + 'Content-Type': `application/json` + } + }; + console.log(url) + while (groupSize > groups.length) { + console.log("groupSize: " + groupSize) + console.log("groups.length: " + groups.length) + try { + console.log(url); + var fetchedGroups = await fetchWithRateLimit(url, requestOptions) + if (fetchedGroups.ok) { + var groupData = await fetchedGroups.json(); + console.log("Total Results: " + groupData.totalResults) + groupSize = groupData.totalResults; + for (var group of groupData.groups) { + groups.push(group); + } + startIndex = startIndex + count; + url.searchParams.delete("startIndex"); + url.searchParams.append("startIndex", startIndex) + } else { + memberSize = 0; + reject(fetchedGroups.status + ": " + fetchedGroups.statusText); + } + } catch (error) { + console.log("Error findGroupById: " + error); + } + } + console.log("Groups found: " + groups.length); + resolve(groups); + }) +} +function whoAmI(bearerToken) { + return new Promise(async function (resolve, reject) { + logger("whoAmI", "Person is having an existential crisis of identify.") + var myHeaders = { + "Authorization": "Bearer " + bearerToken, + "Content-Type": "application/json" + } + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + fetchWithRateLimit("https://webexapis.com/v1/people/me", requestOptions) + .then(async function (response) { + var userInfo = await response.json(); + resolve(userInfo); + }) + .catch(error => reject(error)); + }) +} + +function buildPeopleList(fileName) { + return new Promise(async function (resolve, reject) { + var peoplePromises = []; + var peopleList = []; + if (fileName) { + var csvPeople = fs.readFileSync("./uploads/" + fileName, 'utf-8'); + console.info(csvPeople); + csvPeople.split(/\r?\n/).forEach(line => { + peoplePromises.push(getPersonInfo(line)); + + }) + + Promise.allSettled(peoplePromises) + .then(peopleData => { + //console.log(JSON.stringify(peopleData)) + var cnt = 0; + for (var person of peopleData) { + console.log(JSON.stringify(person)) + cnt++; + if (person.status == "fulfilled") { + if (person.value) { + console.log("Found " + person.value.displayName) + peopleList.push({ + "id": person.value.id, + "type": "peopleList", + "displayName": person.value.displayName + }) + } else { + console.log(JSON.stringify(person)) + } + } else { + console.log(cnt) + console.log(JSON.stringify(person)) + } + } + console.log(JSON.stringify(peopleList)) + resolve(peopleList); + }) + .catch(error => reject(error)); + } + + }) +} + +function getPersonInfo(email) { + return new Promise(async function (resolve, reject) { + var myHeaders = { + "Authorization": "Bearer " + getServiceAccountAccessToken(), + "Content-Type": "application/json" + } + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + fetchWithRateLimit("https://webexapis.com/v1/people?email=" + encodeURIComponent(email) + "&max=100", requestOptions) + .then(async function (response) { + console.log(email + ": " + response.status + ":" + response.statusText) + if (response.ok) { + var userInfo = await response.json(); + resolve(userInfo.items[0]); + } else { + console.log(response.status + ": " + response.statusText) + try { + var stuff = await response.text(); + } catch (error) { + console.log(error) + console.log(stuff) + } + reject(); + } + + }) + .catch(error => { + + reject(error) + }); + + + }) +} + +function checkScheduledJobs() { + return new Promise(async function (resolve, reject) { + + if (jobs.scheduled.length > 0) { + for (var i = 0; i < jobs.scheduled.length; i++) { + if (new Date(jobs.scheduled[i].scheduledFor) < new Date()) { + jobs.running.push(jobs.scheduled[i]); + delete jobs.scheduled[i]; + } else { } + } + + jobs.scheduled = jobs.scheduled.filter(elements => { + return (elements != null && elements !== undefined && elements !== ""); + }); + saveConfig(jobs, "./config/jobs.json"); + processRunningQueue(); + } else { + //console.log("No jobs scheduled."); + } + + }) + +} + +function processRunningQueue() { + return new Promise(async function (resolve, reject) { + var runningQueuePromises = []; + for (var i = 0; i < jobs.running.length; i++) { + jobs.lastJobNumber++; + jobs.running[i].jobId = jobs.lastJobNumber; + jobs.running[i].startTime = new Date(); + jobs.running[i].queueNumber = i; + runningQueuePromises.push(sendQueueJobMessages(i)); + } + + Promise.allSettled(runningQueuePromises) + .then(promises => { + + logger("processRunningQueue", "Completed"); + jobs.running = jobs.running.filter(elements => { return (elements != null && elements !== undefined && elements !== ""); }); + saveConfig(jobs, "./config/jobs.json") + resolve(); + }) + .catch(error => reject(error)); + }) +} + +function sendQueueJobMessages(queueNumber) { + return new Promise(async function (resolve, reject) { + logger("sendQueueJobMessages", "Starting JobId:" + jobs.running[queueNumber].jobId + " Queue#" + queueNumber); + + + var count = 0; + queue.on('active', () => { + console.log(`Working on item #${++count}. Size: ` + queue.size + ` Pending: ` + queue.pending); + }); + + queue.on('completed', async function (result) { + console.log(JSON.stringify(result)); + + }); + + for (var x = 0; x < jobs.running[queueNumber].memberList.length; x++) { + await queue.onSizeLessThan(2); + if (userPrefs[jobs.running[queueNumber].memberList[x].id]) { + queue.add(() => sendRunningQueueMessage(jobs.running[queueNumber].memberList[x].id, jobs.running[queueNumber].message[userPrefs[jobs.running[queueNumber].memberList[x].id].language].markdown, jobs.running[queueNumber].imageName, queueNumber, x, jobs.running[queueNumber].appName)); + } else { + queue.add(() => sendRunningQueueMessage(jobs.running[queueNumber].memberList[x].id, jobs.running[queueNumber].message.english.markdown, jobs.running[queueNumber].imageName, queueNumber, x, jobs.running[queueNumber].appName)); + } + + } + + //console.log(membershipsPromise); + await queue.onIdle(); + var endTime = new Date(); + var startTime = jobs.running[queueNumber].startTime; + var successCnt = 0; + var msgTime = 0; + for (var z = 0; z < jobs.running[queueNumber].memberList.length; z++) { + if (jobs.running[queueNumber].memberList[z].msgTime) { + successCnt++; + msgTime += jobs.running[queueNumber].memberList[z].msgTime; + } + } + jobs.running[queueNumber].endTime = new Date(); + jobs.running[queueNumber].stats = { + "totalTime": msToTime((endTime - startTime)), + "webexTime": msToTime(msgTime), + "averageTime": msToTime((msgTime / successCnt)), + "succeededMsgs": successCnt, + "totalMsgs": jobs.running[queueNumber].memberList.length, + "succeededMsgPct": (successCnt / jobs.running[queueNumber].memberList.length * 100) + } + + jobs.running[queueNumber].endTime = new Date(); + var cardDetails = buildJobCompletedCard(jobs.running[queueNumber]); + jobs.completed.push(jobs.running[queueNumber]); + logger("sendQueueJobMessages", "Completed JobId:" + jobs.running[queueNumber].jobId + " Queue#" + queueNumber) + + await sendDirectCard(jobs.running[queueNumber].senderId, cardDetails, jobs.running[queueNumber].appName) + .then(msgResults => { + delete jobs.running[queueNumber]; + }) + .catch(error => { + //jobs.running[i].memberList[x].error = error; + reject(error); + }) + + resolve(queueNumber); + + }) +} +function sendRunningQueueMessage(toPersonId, message, imageName, jobNumber, memberNumber, appName) { + return new Promise(async function (resolve, reject) { + //logger("sendRunningQueueMessage", toPersonId + "\n" + message + "\n" + imageName + "\n" + jobNumber + "\n" + memberNumber + "\n" + appName) + logger("sendRunningQueueMessage", "Sending to " + toPersonId); + var startTime = new Date(); + + var myHeaders = new Headers(); + myHeaders.append("Authorization", "Bearer " + getBotToken(appName)); + var formdata = new FormData(); + formdata.append("toPersonId", toPersonId); + formdata.append("markdown", message); + + if (imageName) { + var stream = fs.createReadStream("./uploads/" + imageName); + //console.log(file); + formdata.append("files", stream, imageName); + }; + + //var myBody = JSON.stringify(myBodyJSON); + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: formdata, + redirect: 'follow' + }; + + try { + + const response = await fetchAndRetryIfNecessary(async () => ( + await fetch("https://webexapis.com/v1/messages", requestOptions) + )) + + if (response.ok) { + var result = await response.json(); + jobs.running[jobNumber].memberList[memberNumber].results = result; + jobs.running[jobNumber].memberList[memberNumber].msgTime = new Date() - startTime; + logger("sendRunningQueueMessage", "Sent successfully to " + toPersonId); + resolve(result) + } else { + var result = await response.text(); + jobs.running[jobNumber].memberList[memberNumber].results = JSON.parse(result); + jobs.running[jobNumber].memberList[memberNumber].msgTime = new Date() - startTime; + resolve(JSON.parse(result)); + } + + } catch (error) { + logger("sendRunningQueueMessage", "Failed sending to " + toPersonId); + reject(error) + } + }) +} + +function sleep(milliseconds) { + return new Promise((resolve) => setTimeout(resolve, milliseconds)) +} + +function buildJobCompletedCard(job) { + var appName = job.appName; + var botCfg = (config.webex && config.webex.bot && config.webex.bot[appName]) || {}; + var profile = getBotProfile(appName) || {}; + + // Per-bot identity: prefer the live profile from Webex /people/me, fall + // back to the configured label / appName so the card renders even before + // the profile loader finishes. + var botLabel = botCfg.label || profile.displayName || appName; + var botDisplayName = profile.displayName || botLabel; + var avatarUrl = profile.avatar || null; + var title = botLabel + " — Job Summary"; + + var stats = job.stats || {}; + var successPct = (typeof stats.succeededMsgPct === "number") + ? stats.succeededMsgPct.toFixed(1) + "%" + : (stats.succeededMsgPct + "%"); + var startedStr = job.startTime ? new Date(job.startTime).toLocaleString() : "—"; + var completedStr = job.endTime ? new Date(job.endTime).toLocaleString() : "—"; + + // Header columns: bot avatar (if known) + job summary title. + var headerColumns = []; + if (avatarUrl) { + headerColumns.push({ + "type": "Column", + "width": "auto", + "items": [ + { + "type": "Image", + "url": avatarUrl, + "altText": botDisplayName, + "style": "Person", + "size": "Small", + "spacing": "None" + } + ] + }); + } + headerColumns.push({ + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": title, + "wrap": true, + "size": "ExtraLarge", + "weight": "Bolder", + "spacing": "None", + "horizontalAlignment": "Left" + } + ], + "verticalContentAlignment": "Center" + }); + + var jobCompletedCard = { + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.3", + "body": [ + { "type": "ColumnSet", "columns": headerColumns, "spacing": "None" }, + { + "type": "TextBlock", + "text": (job.message && job.message.english && job.message.english.markdown) || "", + "wrap": true + }, + { + "type": "FactSet", + "facts": [ + { "title": "JobID:", "value": (job.jobId !== undefined && job.jobId !== null) ? job.jobId.toString() : "—" }, + { "title": "Started:", "value": startedStr }, + { "title": "Completed:", "value": completedStr }, + { "title": "Duration:", "value": stats.totalTime || "—" }, + { "title": "Avg Message:", "value": stats.averageTime || "—" }, + { "title": "Webex Time:", "value": stats.webexTime || "—" }, + { "title": "Success", "value": (stats.succeededMsgs !== undefined ? (stats.succeededMsgs + "/" + stats.totalMsgs + " (" + successPct + ")") : "—") } + ], + "separator": true + } + ] + }; + + var message = "### " + title + "\n"; + message += "**JobId:** " + (job.jobId !== undefined ? job.jobId : "—") + "\n"; + message += "**Started:** " + startedStr + "\n"; + message += "**Completed:** " + completedStr + "\n"; + message += "**Duration:** " + (stats.totalTime || "—") + "\n"; + message += "**Avg Message:** " + (stats.averageTime || "—") + "\n"; + message += "**Webex Time:** " + (stats.webexTime || "—") + "\n"; + message += "**Success:** " + (stats.succeededMsgs !== undefined ? (stats.succeededMsgs + "/" + stats.totalMsgs + " (" + successPct + ")") : "—") + "\n"; + + return { "card": jobCompletedCard, "message": message }; +} + +async function fetchWithRateLimit(url, requestOptions) { + const response = await fetch(url, requestOptions) + + if (response.status === 429) { + //console.log(response.headers) + const secondsToWait = Number(response.headers.get('retry-after')) + console.log("Waiting for " + secondsToWait.toString() + " due to 429 message: " + url); + console.log(JSON.stringify(requestOptions)); + await new Promise(resolve => setTimeout(resolve, secondsToWait * 1000)) + console.log("Finished waiting for " + secondsToWait.toString() + " due to 429 message: " + url); + console.log(JSON.stringify(requestOptions)) + return await fetchWithRateLimit(url, requestOptions) + } + return response +} + +function getMillisToSleep(retryHeaderString) { + let millisToSleep = Math.round(parseFloat(retryHeaderString) * 1000) + if (isNaN(millisToSleep)) { + millisToSleep = Math.max(0, new Date(retryHeaderString) - new Date()) + } + return millisToSleep +} + +async function fetchAndRetryIfNecessary(callAPIFn) { + const response = await callAPIFn() + if (response.status === 429) { + const retryAfter = response.headers.get('retry-after') + const millisToSleep = getMillisToSleep(retryAfter) + await sleep(millisToSleep) + return fetchAndRetryIfNecessary(callAPIFn) + } + return response +} + +function sendDirectMessage(toPersonId, message, imageName, appName) { + return new Promise(async function (resolve, reject) { + + var startTime = new Date(); + var myHeaders = new Headers(); + myHeaders.append("Authorization", "Bearer " + getBotToken(appName)); + var formdata = new FormData(); + formdata.append("toPersonId", toPersonId); + formdata.append("markdown", message); + + if (imageName) { + //var file = await fs.readFileSync("./uploads/" + imageName); + var stream = await fs.createReadStream("./uploads/" + imageName); + //console.log(file); + formdata.append("files", stream, imageName); + //formdata.set("files", new BlobFromStream(stream, content.length), imageName) + + }; + + //var myBody = JSON.stringify(myBodyJSON); + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: formdata, + redirect: 'follow' + }; + + fetchWithRateLimit("https://webexapis.com/v1/messages", requestOptions) + .then(response => response.text()) + .then(result => { + var msgResult = JSON.parse(result); + resolve(msgResult) + }) + .catch(error => reject(error)); + }); +} + +function sendDirectCard(toPersonId, cardInfo, appName) { + return new Promise(async function (resolve, reject) { + + var myHeaders = { + "Authorization": "Bearer " + getBotToken(appName), + "Content-Type": "application/json" + } + var body = { + toPersonId: toPersonId, + markdown: cardInfo.message, + attachments: [{ contentType: "application/vnd.microsoft.card.adaptive", content: cardInfo.card }] + } + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: JSON.stringify(body), + redirect: 'follow' + }; + //console.log("RequestOptions:" + JSON.stringify(requestOptions)); + fetchWithRateLimit("https://webexapis.com/v1/messages", requestOptions) + .then(async function (response) { + var subData = await response.text(); + //console.log(subData); + resolve(response); + }) + .catch(error => reject(error)); + + }); +} +function buildTranslations(message) { + return new Promise(async function (resolve, reject) { + logger("buildTranslations", "Started"); + var translationPromises = []; + var translations = { + "english": { + "text": message.text, + "markdown": message.markdown, + "html": message.html + } + }; + + for (var language of config.languages) { + translationPromises.push(translateMessage(message.text, language, "text", "text")) + translationPromises.push(translateMessage(message.markdown, language, "text", "markdown")) + translationPromises.push(translateMessage(message.html, language, "html", "html")) + } + + Promise.allSettled(translationPromises) + .then(results => { + //console.log(JSON.stringify(results)) + for (var translation of results) { + if (translation.status == "fulfilled") { + if (translations[translation.value.name]) { + translations[translation.value.name][translation.value.messageType] = translation.value.translatedText; + } else { + translations[translation.value.name] = {}; + translations[translation.value.name][translation.value.messageType] = translation.value.translatedText; + } + + } + } + //console.log(JSON.stringify(translations)); + logger("buildTranslations", "Completed") + resolve(translations); + }) + }) +} + +function translateMessage(message, language, format, messageType) { + return new Promise(async function (resolve, reject) { + var urlSearchParams = { + q: message, + target: language.key, + format: format, + source: "en", + model: "nmt", + key: process.env.GOOGLE_TRANSLATE_API_KEY || '' + } + var translateUrl = "https://translation.googleapis.com/language/translate/v2?" + new URLSearchParams(urlSearchParams); + //console.log(translateUrl); + fetchWithRateLimit(translateUrl, { "method": "POST" }) + .then(response => response.json()) + .then(result => { + console.log(JSON.stringify.result); + var languageResult = { + "name": language.name, + "key": language.key, + "format": format, + "messageType": messageType, + "translatedText": result.data.translations[0].translatedText + } + resolve(languageResult); + }) + .catch(error => reject(error)) + }) +} +function refreshToken() { + return new Promise(async function (resolve, reject) { + var url = "https://webexapis.com/v1/access_token"; + + var params = new URLSearchParams(); + params.append('grant_type', 'refresh_token'); + params.append('client_id', process.env.WEBEX_SERVICE_ACCOUNT_CLIENT_ID || ''); + params.append('client_secret', process.env.WEBEX_SERVICE_ACCOUNT_CLIENT_SECRET || ''); + params.append('refresh_token', serviceAccountToken.refresh_token); + + fetchWithRateLimit(url, { method: 'post', body: params }) + .then(res => { + console.log(res.status); + return res.json(); + }) + .then(function (json) { + var updated = Object.assign({}, json, { + created: new Date().toLocaleString("en-US", { timeZoneName: "short", timeZone: "America/New_York" }), + renewBy: new Date(Date.now() + (json.expires_in * 1000)).toLocaleString("en-US", { timeZoneName: "short", timeZone: "America/New_York" }), + refreshBy: new Date(Date.now() + (json.refresh_token_expires_in * 1000)).toLocaleString("en-US", { timeZoneName: "short", timeZone: "America/New_York" }) + }); + saveServiceAccountToken(updated); + resolve("Refresh Success!\nExpires: " + updated.renewBy + "\nRefreshBy: " + updated.refreshBy); + }) + .catch(err => reject(err)); + }) +} + +function saveConfig(jsonObject, configFile) { + const jsonData = JSON.stringify(jsonObject, null, 4); + fs.writeFileSync(configFile, jsonData, (err) => { + if (err) { + throw err; + } else { + logger("saveConfig", "Wrote " + configFile); + } + }) +} + +// Completed jobs are kept for review for COMPLETED_RETENTION_DAYS. Anything +// older is dropped from jobs.completed by the daily cron. Bumping this number +// just means we hold more history (and a bigger jobs.json) on disk. +const COMPLETED_RETENTION_DAYS = 30; + +async function cleanCompletedJobs(jobs) { + var cutoffDate = new Date(Date.now() - (COMPLETED_RETENTION_DAYS * 24 * 60 * 60 * 1000)); + if (!Array.isArray(jobs.completed)) { + console.log('No completed array found – nothing to do.'); + return jobs; + } + + const originalCount = jobs.completed.length; + + jobs.completed = jobs.completed.filter(job => { + // Prefer endTime, fall back to startTime if missing + const jobDateStr = job.endTime || job.startTime || job.created; + if (!jobDateStr) return true; // safety: keep if no date + + const jobDate = new Date(jobDateStr); + return jobDate >= cutoffDate; + }); + + const removed = originalCount - jobs.completed.length; + console.log(`Removed ${removed} job(s) older than ${COMPLETED_RETENTION_DAYS} days (cutoff ${cutoffDate.toISOString().split('T')[0]}).`); + console.log(`Remaining completed jobs: ${jobs.completed.length}`); + + return jobs; +} + +function isAuthorized(appName, personId) { + logger("isAuthorized", appName + " " + personId) + var botCfg = getBotConfig(appName); + if (!botCfg) return false; + if (!personId) return false; + return !!(botCfg.authorized && botCfg.authorized[personId]); +} + +function msToTime(duration) { + var milliseconds = parseInt((duration % 1000) / 100) + , seconds = parseInt((duration / 1000) % 60) + , minutes = parseInt((duration / (1000 * 60)) % 60) + , hours = parseInt((duration / (1000 * 60 * 60)) % 24); + + //hours = (hours < 10) ? "0" + hours : hours; + //minutes = (minutes < 10) ? "0" + minutes : minutes; + //seconds = (seconds < 10) ? "0" + seconds : seconds; + + var resultTime = ""; + if (hours > 0) { + resultTime = hours + "h " + minutes + "m " + seconds + "." + milliseconds + "s"; + } else if (minutes > 0) { + resultTime = minutes + "m " + seconds + "." + milliseconds + "s"; + } else { + resultTime = seconds + "." + milliseconds + "s"; + } + + return resultTime; +} + +function logger(activeFunction, logLine) { + var d = new Date(); + console.log(d.toLocaleString() + " " + activeFunction + ": " + logLine); +} + +// gracefully shutdown (ctrl-c) +process.on('SIGINT', function () { + server.close(() => { + logger("shutdown", config.server.name + ' stopped!') + process.exit(); + }); +}) diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b58ec40 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1500 @@ +{ + "name": "collabcentral", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "collabcentral", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", + "express": "^4.18.2", + "form-data": "^4.0.0", + "multer": "^1.4.5-lts.1", + "node-cron": "^3.0.2", + "node-fetch": "^3.3.2", + "p-queue": "^7.4.1", + "sharp": "^0.32.6", + "uuid": "^9.0.1" + } + }, + "node_modules/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==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/b4a": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.4.tgz", + "integrity": "sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/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==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/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==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/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==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/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==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/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==" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/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==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.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" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/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==", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/express/node_modules/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==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/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==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + }, + "node_modules/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==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/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==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", + "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==" + }, + "node_modules/node-cron": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.2.tgz", + "integrity": "sha512-iP8l0yGlNpE0e6q1o185yOApANRe47UPbLf4YxfbiNHt/RU5eBcGB/e0oudruheSf+LQeDMezqC5BVAb5wwRcQ==", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-queue": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-7.4.1.tgz", + "integrity": "sha512-vRpMXmIkYF2/1hLBKisKeVYJZ8S2tZ0zEAmIJgdVKP2nq0nh4qCdf8bgw+ZgKrkh71AOCaqzwbJJk1WtdcF3VA==", + "dependencies": { + "eventemitter3": "^5.0.1", + "p-timeout": "^5.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-timeout": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz", + "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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==" + }, + "node_modules/prebuild-install": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.1.tgz", + "integrity": "sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/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==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/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==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/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==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "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" + } + }, + "node_modules/readable-stream/node_modules/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==" + }, + "node_modules/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==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/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==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/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==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/sharp": { + "version": "0.32.6", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.32.6.tgz", + "integrity": "sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.2", + "node-addon-api": "^6.1.0", + "prebuild-install": "^7.1.1", + "semver": "^7.5.4", + "simple-get": "^4.0.1", + "tar-fs": "^3.0.4", + "tunnel-agent": "^0.6.0" + }, + "engines": { + "node": ">=14.15.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/streamx": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.15.1.tgz", + "integrity": "sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + } + }, + "node_modules/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==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/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==" + }, + "node_modules/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==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, + "node_modules/tar-stream": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.6.tgz", + "integrity": "sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/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==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f0e1aa0 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "collabcentral", + "version": "1.0.0", + "description": "Micro-service for sending messages to the organization.", + "main": "index.js", + "scripts": { + "start": "node --env-file=.env index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Joseph B. McQueen", + "license": "ISC", + "type": "module", + "dependencies": { + "body-parser": "^1.20.2", + "cookie-parser": "^1.4.6", + "express": "^4.18.2", + "form-data": "^4.0.0", + "multer": "^1.4.5-lts.1", + "node-cron": "^3.0.2", + "node-fetch": "^3.3.2", + "p-queue": "^7.4.1", + "sharp": "^0.32.6", + "uuid": "^9.0.1" + } +} diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 0000000..e69de29