Explorar el Código

aipApp first commit

junggilpark hace 1 año
commit
214058a7fa
Se han modificado 47 ficheros con 5533 adiciones y 0 borrados
  1. 21 0
      .gitignore
  2. 5 0
      .vscode/extensions.json
  3. 247 0
      .vscode/launch.json
  4. 11 0
      .vscode/settings.json
  5. 78 0
      .vscode/tasks.json
  6. 24 0
      .webappignore
  7. 67 0
      README.md
  8. BIN
      appPackage/color.png
  9. 46 0
      appPackage/manifest.json
  10. BIN
      appPackage/outline.png
  11. 15 0
      env/.env.dev
  12. 11 0
      env/.env.test
  13. 53 0
      infra/azure.bicep
  14. 12 0
      infra/azure.parameters.json
  15. 2970 0
      package-lock.json
  16. 37 0
      package.json
  17. 189 0
      src/app.js
  18. 272 0
      src/auth/AuthProvider.js
  19. 40 0
      src/authConfig.js
  20. 60 0
      src/fetch.js
  21. 31 0
      src/routes/auth.js
  22. 17 0
      src/routes/index.js
  23. 41 0
      src/routes/users.js
  24. 11 0
      src/secrets.js
  25. BIN
      src/static/favicon.ico
  26. BIN
      src/static/images/chevron-up.png
  27. BIN
      src/static/images/chevrondown.png
  28. 20 0
      src/static/images/empty_folder.svg
  29. BIN
      src/static/images/file.png
  30. BIN
      src/static/images/folder.png
  31. BIN
      src/static/images/menu-icon.png
  32. BIN
      src/static/images/onedrive.png
  33. 1 0
      src/static/images/onetoc.svg
  34. 1 0
      src/static/images/pdf.svg
  35. 1 0
      src/static/images/photo.svg
  36. BIN
      src/static/images/plus.png
  37. 1 0
      src/static/images/pptx.svg
  38. BIN
      src/static/images/refresh.png
  39. 1 0
      src/static/images/txt.svg
  40. BIN
      src/static/images/upload.png
  41. 1 0
      src/static/images/xlsx.svg
  42. 18 0
      src/static/scripts/teamsapp.js
  43. 306 0
      src/static/styles/custom.css
  44. 647 0
      src/views/hello.html
  45. 81 0
      teamsapp.local.yml
  46. 137 0
      teamsapp.yml
  47. 60 0
      web.config

+ 21 - 0
.gitignore

@@ -0,0 +1,21 @@
+# TeamsFx files
+env/.env.*.user
+env/.env.local
+.DS_Store
+.localConfigs
+build
+appPackage/build
+
+# dependencies
+/node_modules
+
+# testing
+/coverage
+
+# production
+/build
+
+# misc
+.DS_Store
+.env
+.deployment

+ 5 - 0
.vscode/extensions.json

@@ -0,0 +1,5 @@
+{
+  "recommendations": [
+    "TeamsDevApp.ms-teams-vscode-extension"
+  ]
+}

+ 247 - 0
.vscode/launch.json

@@ -0,0 +1,247 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "Launch Remote in Teams (Edge)",
+            "type": "msedge",
+            "request": "launch",
+            "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+            "presentation": {
+                "group": "group 1: Teams",
+                "order": 3
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Launch Remote in Teams (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+            "presentation": {
+                "group": "group 1: Teams",
+                "order": 3
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Launch Remote in Outlook (Edge)",
+            "type": "msedge",
+            "request": "launch",
+            "url": "https://outlook.office.com/host/${{M365_APP_ID}}?${account-hint}",
+            "presentation": {
+                "group": "group 2: Outlook",
+                "order": 3
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Launch Remote in Outlook (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "https://outlook.office.com/host/${{M365_APP_ID}}?${account-hint}",
+            "presentation": {
+                "group": "group 2: Outlook",
+                "order": 3
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Launch Remote in the Microsoft 365 app (Edge)",
+            "type": "msedge",
+            "request": "launch",
+            "url": "https://www.office.com/m365apps/${{M365_APP_ID}}?auth=2&${account-hint}",
+            "presentation": {
+                "group": "group 3: the Microsoft 365 app",
+                "order": 3
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Launch Remote in the Microsoft 365 app (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "https://www.office.com/m365apps/${{M365_APP_ID}}?auth=2&${account-hint}",
+            "presentation": {
+                "group": "group 3: the Microsoft 365 app",
+                "order": 3
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Frontend in Teams (Edge)",
+            "type": "msedge",
+            "request": "launch",
+            "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+            "cascadeTerminateToConfigurations": [
+                "Attach to Local Service"
+            ],
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Frontend in Teams (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
+            "cascadeTerminateToConfigurations": [
+                "Attach to Local Service"
+            ],
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Frontend in Outlook (Edge)",
+            "type": "msedge",
+            "request": "launch",
+            "url": "https://outlook.office.com/host/${{local:M365_APP_ID}}?${account-hint}",
+            "cascadeTerminateToConfigurations": [
+                "Attach to Local Service"
+            ],
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Frontend in Outlook (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "https://outlook.office.com/host/${{local:M365_APP_ID}}?${account-hint}",
+            "cascadeTerminateToConfigurations": [
+                "Attach to Local Service"
+            ],
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Frontend in the Microsoft 365 app (Edge)",
+            "type": "msedge",
+            "request": "launch",
+            "url": "https://www.office.com/m365apps/${{local:M365_APP_ID}}?auth=2&${account-hint}",
+            "cascadeTerminateToConfigurations": [
+                "Attach to Local Service"
+            ],
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Frontend in the Microsoft 365 app (Chrome)",
+            "type": "chrome",
+            "request": "launch",
+            "url": "https://www.office.com/m365apps/${{local:M365_APP_ID}}?auth=2&${account-hint}",
+            "cascadeTerminateToConfigurations": [
+                "Attach to Local Service"
+            ],
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        },
+        {
+            "name": "Attach to Local Service",
+            "type": "node",
+            "request": "attach",
+            "port": 9239,
+            "restart": true,
+            "presentation": {
+                "group": "all",
+                "hidden": true
+            },
+            "internalConsoleOptions": "neverOpen"
+        }
+    ],
+    "compounds": [
+        {
+            "name": "Debug in Teams (Edge)",
+            "configurations": [
+                "Attach to Frontend in Teams (Edge)",
+                "Attach to Local Service"
+            ],
+            "preLaunchTask": "Start Teams App Locally",
+            "presentation": {
+                "group": "group 1: Teams",
+                "order": 1
+            },
+            "stopAll": true
+        },
+        {
+            "name": "Debug in Teams (Chrome)",
+            "configurations": [
+                "Attach to Frontend in Teams (Chrome)",
+                "Attach to Local Service"
+            ],
+            "preLaunchTask": "Start Teams App Locally",
+            "presentation": {
+                "group": "group 1: Teams",
+                "order": 2
+            },
+            "stopAll": true
+        },
+        {
+            "name": "Debug in Outlook (Edge)",
+            "configurations": [
+                "Attach to Frontend in Outlook (Edge)",
+                "Attach to Local Service"
+            ],
+            "preLaunchTask": "Start Teams App Locally",
+            "presentation": {
+                "group": "group 2: Outlook",
+                "order": 1
+            },
+            "stopAll": true
+        },
+        {
+            "name": "Debug in Outlook (Chrome)",
+            "configurations": [
+                "Attach to Frontend in Outlook (Chrome)",
+                "Attach to Local Service"
+            ],
+            "preLaunchTask": "Start Teams App Locally",
+            "presentation": {
+                "group": "group 2: Outlook",
+                "order": 2
+            },
+            "stopAll": true
+        },
+        {
+            "name": "Debug in the Microsoft 365 app (Edge)",
+            "configurations": [
+                "Attach to Frontend in the Microsoft 365 app (Edge)",
+                "Attach to Local Service"
+            ],
+            "preLaunchTask": "Start Teams App Locally",
+            "presentation": {
+                "group": "group 3: the Microsoft 365 app",
+                "order": 1
+            },
+            "stopAll": true
+        },
+        {
+            "name": "Debug in the Microsoft 365 app (Chrome)",
+            "configurations": [
+                "Attach to Frontend in the Microsoft 365 app (Chrome)",
+                "Attach to Local Service"
+            ],
+            "preLaunchTask": "Start Teams App Locally",
+            "presentation": {
+                "group": "group 3: the Microsoft 365 app",
+                "order": 2
+            },
+            "stopAll": true
+        }
+    ]
+}

+ 11 - 0
.vscode/settings.json

@@ -0,0 +1,11 @@
+{
+    "debug.onTaskErrors": "abort",
+    "json.schemas": [
+        {
+            "fileMatch": [
+                "/aad.*.json"
+            ],
+            "schema": {}
+        }
+    ]
+}

+ 78 - 0
.vscode/tasks.json

@@ -0,0 +1,78 @@
+// This file is automatically generated by Teams Toolkit.
+// The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0.
+// See https://aka.ms/teamsfx-tasks for details on how to customize each task.
+{
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "Start Teams App Locally",
+            "dependsOn": [
+                "Validate prerequisites",
+                "Provision",
+                "Deploy",
+                "Start application"
+            ],
+            "dependsOrder": "sequence"
+        },
+        {
+            // Check all required prerequisites.
+            // See https://aka.ms/teamsfx-tasks/check-prerequisites to know the details and how to customize the args.
+            "label": "Validate prerequisites",
+            "type": "teamsfx",
+            "command": "debug-check-prerequisites",
+            "args": {
+                "prerequisites": [
+                    "nodejs", // Validate if Node.js is installed.
+                    "m365Account", // Sign-in prompt for Microsoft 365 account, then validate if the account enables the sideloading permission.
+                    "portOccupancy" // Validate available ports to ensure those debug ones are not occupied.
+                ],
+                "portOccupancy": [
+                    53000, // tab service port,
+                    9239 // app inspector port for Node.js debugger
+                ]
+            }
+        },
+        {
+            // Create the debug resources.
+            // See https://aka.ms/teamsfx-tasks/provision to know the details and how to customize the args.
+            "label": "Provision",
+            "type": "teamsfx",
+            "command": "provision",
+            "args": {
+                "env": "local"
+            }
+        },
+        {
+            // Build project.
+            // See https://aka.ms/teamsfx-tasks/deploy to know the details and how to customize the args.
+            "label": "Deploy",
+            "type": "teamsfx",
+            "command": "deploy",
+            "args": {
+                "env": "local"
+            }
+        },
+        {
+            "label": "Start application",
+            "type": "shell",
+            "command": "npm run dev:teamsfx",
+            "isBackground": true,
+            "options": {
+                "cwd": "${workspaceFolder}"
+            },
+            "problemMatcher": {
+                "pattern": {
+                    "regexp": "^.*$",
+                    "file": 0,
+                    "location": 1,
+                    "message": 2
+                },
+                "background": {
+                    "activeOnStart": true,
+                    "beginsPattern": ".*",
+                    "endsPattern": "Compiled|Failed|compiled|failed|listening"
+                }
+            }
+        }
+    ]
+}

+ 24 - 0
.webappignore

@@ -0,0 +1,24 @@
+.webappignore
+.fx
+.deployment
+.localSettings
+.vscode
+*.js.map
+*.ts.map
+*.ts
+.git*
+.tsbuildinfo
+CHANGELOG.md
+readme.md
+local.settings.json
+test
+tsconfig.json
+.DS_Store
+teamsapp.yml
+teamsapp.*.yml
+/env/
+/node_modules/.bin
+/node_modules/ts-node
+/node_modules/typescript
+/appPackage/
+/infra/

+ 67 - 0
README.md

@@ -0,0 +1,67 @@
+# Overview of the Basic Tab template
+
+This template showcases how Microsoft Teams supports the ability to run web-based UI inside "custom tabs" that users can install either for just themselves (personal tabs) or within a team or group chat context.
+
+## Get started with the Basic Tab template
+
+> **Prerequisites**
+>
+> To run the basic tab template in your local dev machine, you will need:
+>
+> - [Node.js](https://nodejs.org/), supported versions: 16, 18
+> - A [Microsoft 365 account for development](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts)
+> - [Set up your dev environment for extending Teams apps across Microsoft 365](https://aka.ms/teamsfx-m365-apps-prerequisites)
+> Please note that after you enrolled your developer tenant in Office 365 Target Release, it may take couple days for the enrollment to take effect.
+> - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit) version 5.0.0 and higher or [Teams Toolkit CLI](https://aka.ms/teams-toolkit-cli)
+
+1. First, select the Teams Toolkit icon on the left in the VS Code toolbar.
+2. In the Account section, sign in with your [Microsoft 365 account](https://docs.microsoft.com/microsoftteams/platform/toolkit/accounts) if you haven't already.
+3. Press F5 to start debugging which launches your app in Teams using a web browser. Select `Debug in Teams (Edge)` or `Debug in Teams (Chrome)`.
+4. When Teams launches in the browser, select the Add button in the dialog to install your app to Teams.
+
+**Congratulations**! You are running an application that can now show a basic web page in Teams, Outlook and the Microsoft 365 app.
+
+![Basic Tab](https://github.com/OfficeDev/TeamsFx/assets/11220663/ad7bd534-cdb2-4c18-a71b-b206b6387b4c)
+
+## What's included in the template
+
+| Folder       | Contents                                            |
+| - | - |
+| `.vscode`    | VSCode files for debugging                          |
+| `appPackage` | Templates for the Teams application manifest        |
+| `env`        | Environment files                                   |
+| `infra`      | Templates for provisioning Azure resources          |
+| `src`        | The source code for the Teams application |
+
+The following files can be customized and demonstrate an example implementation to get you started.
+
+| File                                 | Contents                                           |
+| - | - |
+|`src/static/scripts/teamsapp.js`|A script that calls `teamsjs` SDK to get the context of on which Microsoft 365 application your app is running.|
+|`src/static/styles/custom.css`|css file for the app.|
+|`src/static/views/hello.html`|html file for the app.|
+|`src/app.js`|Starting a restify server.|
+
+The following are Teams Toolkit specific project files. You can [visit a complete guide on Github](https://github.com/OfficeDev/TeamsFx/wiki/Teams-Toolkit-Visual-Studio-Code-v5-Guide#overview) to understand how Teams Toolkit works.
+
+| File                                 | Contents                                           |
+| - | - |
+|`teamsapp.yml`|This is the main Teams Toolkit project file. The project file defines two primary things:  Properties and configuration Stage definitions. |
+|`teamsapp.local.yml`|This overrides `teamsapp.yml` with actions that enable local execution and debugging.|
+
+## Extend the Basic Tab template
+
+Following documentation will help you to extend the Basic Tab template.
+
+- [Add or manage the environment](https://learn.microsoft.com/microsoftteams/platform/toolkit/teamsfx-multi-env)
+- [Create multi-capability app](https://learn.microsoft.com/microsoftteams/platform/toolkit/add-capability)
+- [Add single sign on to your app](https://learn.microsoft.com/microsoftteams/platform/toolkit/add-single-sign-on)
+- [Access data in Microsoft Graph](https://learn.microsoft.com/microsoftteams/platform/toolkit/teamsfx-sdk#microsoft-graph-scenarios)
+- [Use an existing Microsoft Entra application](https://learn.microsoft.com/microsoftteams/platform/toolkit/use-existing-aad-app)
+- [Customize the Teams app manifest](https://learn.microsoft.com/microsoftteams/platform/toolkit/teamsfx-preview-and-customize-app-manifest)
+- Host your app in Azure by [provision cloud resources](https://learn.microsoft.com/microsoftteams/platform/toolkit/provision) and [deploy the code to cloud](https://learn.microsoft.com/microsoftteams/platform/toolkit/deploy)
+- [Collaborate on app development](https://learn.microsoft.com/microsoftteams/platform/toolkit/teamsfx-collaboration)
+- [Set up the CI/CD pipeline](https://learn.microsoft.com/microsoftteams/platform/toolkit/use-cicd-template)
+- [Publish the app to your organization or the Microsoft Teams app store](https://learn.microsoft.com/microsoftteams/platform/toolkit/publish)
+- [Enable the app for multi-tenant](https://github.com/OfficeDev/TeamsFx/wiki/Multi-tenancy-Support-for-Azure-AD-app)
+- [Preview the app on mobile clients](https://github.com/OfficeDev/TeamsFx/wiki/Run-and-debug-your-Teams-application-on-iOS-or-Android-client)

BIN
appPackage/color.png


+ 46 - 0
appPackage/manifest.json

@@ -0,0 +1,46 @@
+{
+    "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
+    "manifestVersion": "1.16",
+    "version": "1.0.0",
+    "id": "${{TEAMS_APP_ID}}",
+    "packageName": "com.microsoft.teams.extension",
+    "developer": {
+        "name": "Teams App, Inc.",
+        "websiteUrl": "https://www.example.com",
+        "privacyUrl": "https://www.example.com/privacy",
+        "termsOfUseUrl": "https://www.example.com/termsofuse"
+    },
+    "icons": {
+        "color": "color.png",
+        "outline": "outline.png"
+    },
+    "name": {
+        "short": "share",
+        "full": "Full name for share"
+    },
+    "description": {
+        "short": "Short description of share",
+        "full": "Full description of share"
+    },
+    "accentColor": "#FFFFFF",
+    "bots": [],
+    "composeExtensions": [],
+    "staticTabs": [
+        {
+            "entityId": "index0",
+            "name": "Personal Tab",
+            "contentUrl": "${{TAB_ENDPOINT}}/tab",
+            "websiteUrl": "${{TAB_ENDPOINT}}/tab",
+            "scopes": [
+                "personal"
+            ]
+        }
+    ],
+    "permissions": [
+        "identity",
+        "messageTeamMembers"
+    ],
+    "validDomains": [
+        "${{TAB_DOMAIN}}"
+    ]
+}

BIN
appPackage/outline.png


+ 15 - 0
env/.env.dev

@@ -0,0 +1,15 @@
+# This file includes environment variables that will be committed to git by default.
+
+# Built-in environment variables
+TEAMSFX_ENV=dev
+APP_NAME_SUFFIX=dev
+
+# Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups.
+AZURE_SUBSCRIPTION_ID=
+AZURE_RESOURCE_GROUP_NAME=
+RESOURCE_SUFFIX=
+
+# Generated during provision, you can also add your own variables.
+TEAMS_APP_ID=
+TAB_AZURE_STORAGE_RESOURCE_ID=
+TAB_ENDPOINT=

+ 11 - 0
env/.env.test

@@ -0,0 +1,11 @@
+CLOUD_INSTANCE="https://login.microsoftonline.com/" # cloud instance string should end with a trailing slash
+TENANT_ID="2e58414a-c6ae-43ff-aaf5-45ab8b78a404"
+CLIENT_ID="0673c862-7edc-455d-9636-5559a0a8f2b9"
+CLIENT_SECRET="xhZ8Q~9z1kuxKs~wt0EWbrtuG5FFlyS7WE8~~afa"
+
+REDIRECT_URI="https://localhost:53000/redirect/"
+POST_LOGOUT_REDIRECT_URI="https://localhost:53000"
+
+GRAPH_API_ENDPOINT="https://graph.microsoft.com/" # graph api endpoint string should end with a trailing slash
+
+EXPRESS_SESSION_SECRET="SESSION_SECRET"

+ 53 - 0
infra/azure.bicep

@@ -0,0 +1,53 @@
+@maxLength(20)
+@minLength(4)
+@description('Used to generate names for all resources in this file')
+param resourceBaseName string
+
+param webAppSku string
+
+param serverfarmsName string = resourceBaseName
+param webAppName string = resourceBaseName
+param location string = resourceGroup().location
+
+// Compute resources for your Web App
+resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = {
+  kind: 'app'
+  location: location
+  name: serverfarmsName
+  sku: {
+    name: webAppSku
+  }
+}
+
+// Azure Web App that hosts your website
+resource webApp 'Microsoft.Web/sites@2021-02-01' = {
+  kind: 'app'
+  location: location
+  name: webAppName
+  properties: {
+    serverFarmId: serverfarm.id
+    httpsOnly: true
+    siteConfig: {
+      appSettings: [
+        {
+          name: 'WEBSITE_RUN_FROM_PACKAGE'
+          value: '1' // Run Azure App Service from a package file
+        }
+        {
+          name: 'WEBSITE_NODE_DEFAULT_VERSION'
+          value: '~18' // Set NodeJS version to 18.x for your site
+        }
+        {
+          name: 'RUNNING_ON_AZURE'
+          value: '1'
+        }
+      ]
+      ftpsState: 'FtpsOnly'
+    }
+  }
+}
+
+// The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
+output TAB_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id // used in deploy stage
+output TAB_DOMAIN string = webApp.properties.defaultHostName
+output TAB_ENDPOINT string = 'https://${webApp.properties.defaultHostName}'

+ 12 - 0
infra/azure.parameters.json

@@ -0,0 +1,12 @@
+{
+    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
+    "contentVersion": "1.0.0.0",
+    "parameters": {
+      "resourceBaseName": {
+        "value": "tab${{RESOURCE_SUFFIX}}"
+      },
+      "webAppSku": {
+        "value": "F1"
+      }
+    }
+  }

+ 2970 - 0
package-lock.json

@@ -0,0 +1,2970 @@
+{
+    "name": "share",
+    "version": "0.1.0",
+    "lockfileVersion": 3,
+    "requires": true,
+    "packages": {
+        "": {
+            "name": "share",
+            "version": "0.1.0",
+            "dependencies": {
+                "@azure/communication-identity": "^1.3.1",
+                "@azure/identity": "^4.0.1",
+                "@azure/msal-node": "^2.6.6",
+                "@microsoft/microsoft-graph-client": "^3.0.7",
+                "@microsoft/teams-js": "^2.21.0",
+                "axios": "^1.6.8",
+                "cookie-parser": "^1.4.6",
+                "cors": "^2.8.5",
+                "dote": "^1.1.0",
+                "dotenv": "^16.4.5",
+                "env": "^0.0.2",
+                "express": "^4.19.2",
+                "express-session": "^1.18.0",
+                "fs": "^0.0.1-security",
+                "path": "^0.12.7",
+                "restify": "^11.1.0",
+                "send": "^0.18.0"
+            },
+            "devDependencies": {
+                "env-cmd": "^10.1.0",
+                "nodemon": "^2.0.21"
+            },
+            "engines": {
+                "node": "16 || 18"
+            }
+        },
+        "node_modules/@azure/abort-controller": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
+            "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
+            "dependencies": {
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            }
+        },
+        "node_modules/@azure/communication-common": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/@azure/communication-common/-/communication-common-2.3.1.tgz",
+            "integrity": "sha512-6ZQt20iMZbyckQn4m1TDwiDv3Fzyt1h4lnQ1szBBns2x3VQY9XHbnskPtvUdwK/HT+c/1PoUwof3toy1AIznbQ==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "@azure/core-auth": "^1.3.0",
+                "@azure/core-rest-pipeline": "^1.3.2",
+                "@azure/core-tracing": "^1.0.0",
+                "@azure/core-util": "^1.0.0",
+                "events": "^3.0.0",
+                "jwt-decode": "^4.0.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/communication-identity": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/@azure/communication-identity/-/communication-identity-1.3.1.tgz",
+            "integrity": "sha512-S54UTeEM3SbUNGFeGcGQEw64KLUu8CmZi2/2hRu3sy6Rx2i4Y8aL+ITlGC7bOm2v5rKELD5PoXAVYDJxVCJN1w==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "@azure/communication-common": "^2.3.1",
+                "@azure/core-auth": "^1.3.0",
+                "@azure/core-client": "^1.3.0",
+                "@azure/core-lro": "^2.2.0",
+                "@azure/core-paging": "^1.1.1",
+                "@azure/core-rest-pipeline": "^1.3.0",
+                "@azure/core-tracing": "^1.0.0",
+                "@azure/logger": "^1.0.0",
+                "events": "^3.0.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-auth": {
+            "version": "1.7.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.7.1.tgz",
+            "integrity": "sha512-dyeQwvgthqs/SlPVQbZQetpslXceHd4i5a7M/7z/lGEAVwnSluabnQOjF2/dk/hhWgMISusv1Ytp4mQ8JNy62A==",
+            "dependencies": {
+                "@azure/abort-controller": "^2.0.0",
+                "@azure/core-util": "^1.1.0",
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-client": {
+            "version": "1.9.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.1.tgz",
+            "integrity": "sha512-hHYFx9lz0ZpbO5W+iotU9tmIX1jPcoIjYUEUaWGuMi1628LCQ/z05TUR4P+NRtMgyoHQuyVYyGQiD3PC47kaIA==",
+            "dependencies": {
+                "@azure/abort-controller": "^2.0.0",
+                "@azure/core-auth": "^1.4.0",
+                "@azure/core-rest-pipeline": "^1.9.1",
+                "@azure/core-tracing": "^1.0.0",
+                "@azure/core-util": "^1.6.1",
+                "@azure/logger": "^1.0.0",
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-client/node_modules/@azure/abort-controller": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-lro": {
+            "version": "2.7.2",
+            "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz",
+            "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==",
+            "dependencies": {
+                "@azure/abort-controller": "^2.0.0",
+                "@azure/core-util": "^1.2.0",
+                "@azure/logger": "^1.0.0",
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz",
+            "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-paging": {
+            "version": "1.6.2",
+            "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz",
+            "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-rest-pipeline": {
+            "version": "1.15.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.15.1.tgz",
+            "integrity": "sha512-ZxS6i3eHxh86u+1eWZJiYywoN2vxvsSoAUx60Mny8cZ4nTwvt7UzVVBJO+m2PW2KIJfNiXMt59xBa59htOWL4g==",
+            "dependencies": {
+                "@azure/abort-controller": "^2.0.0",
+                "@azure/core-auth": "^1.4.0",
+                "@azure/core-tracing": "^1.0.1",
+                "@azure/core-util": "^1.3.0",
+                "@azure/logger": "^1.0.0",
+                "http-proxy-agent": "^7.0.0",
+                "https-proxy-agent": "^7.0.0",
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-tracing": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.1.1.tgz",
+            "integrity": "sha512-qPbYhN1pE5XQ2jPKIHP33x8l3oBu1UqIWnYqZZ3OYnYjzY0xqIHjn49C+ptsPD9yC7uyWI9Zm7iZUZLs2R4DhQ==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-util": {
+            "version": "1.8.1",
+            "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.8.1.tgz",
+            "integrity": "sha512-L3voj0StUdJ+YKomvwnTv7gHzguJO+a6h30pmmZdRprJCM+RJlGMPxzuh4R7lhQu1jNmEtaHX5wvTgWLDAmbGQ==",
+            "dependencies": {
+                "@azure/abort-controller": "^2.0.0",
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/core-util/node_modules/@azure/abort-controller": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.1.tgz",
+            "integrity": "sha512-NhzeNm5zu2fPlwGXPUjzsRCRuPx5demaZyNcyNYJDqpa/Sbxzvo/RYt9IwUaAOnDW5+r7J9UOE6f22TQnb9nhQ==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/identity": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.0.1.tgz",
+            "integrity": "sha512-yRdgF03SFLqUMZZ1gKWt0cs0fvrDIkq2bJ6Oidqcoo5uM85YMBnXWMzYKK30XqIT76lkFyAaoAAy5knXhrG4Lw==",
+            "dependencies": {
+                "@azure/abort-controller": "^1.0.0",
+                "@azure/core-auth": "^1.5.0",
+                "@azure/core-client": "^1.4.0",
+                "@azure/core-rest-pipeline": "^1.1.0",
+                "@azure/core-tracing": "^1.0.0",
+                "@azure/core-util": "^1.3.0",
+                "@azure/logger": "^1.0.0",
+                "@azure/msal-browser": "^3.5.0",
+                "@azure/msal-node": "^2.5.1",
+                "events": "^3.0.0",
+                "jws": "^4.0.0",
+                "open": "^8.0.0",
+                "stoppable": "^1.1.0",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/logger": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.1.1.tgz",
+            "integrity": "sha512-/+4TtokaGgC+MnThdf6HyIH9Wrjp+CnCn3Nx3ggevN7FFjjNyjqg0yLlc2i9S+Z2uAzI8GYOo35Nzb1MhQ89MA==",
+            "dependencies": {
+                "tslib": "^2.6.2"
+            },
+            "engines": {
+                "node": ">=18.0.0"
+            }
+        },
+        "node_modules/@azure/msal-browser": {
+            "version": "3.11.1",
+            "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-3.11.1.tgz",
+            "integrity": "sha512-tZFJnP5ZpgkmazSriEDW+Xl3/4WI823uhnYhWCHPkGywFWEZoPA5VkiCK8x4x8ECXp3mGr5qEI82MU43PBiaKA==",
+            "dependencies": {
+                "@azure/msal-common": "14.8.1"
+            },
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
+        "node_modules/@azure/msal-common": {
+            "version": "14.8.1",
+            "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-14.8.1.tgz",
+            "integrity": "sha512-9HfBMDTIgtFFkils+o6gO/aGEoLLuc4z+QLLfhy/T1bTNPiVsX/9CjaBPMZGnMltN/IlMkU5SGGNggGh55p5xA==",
+            "engines": {
+                "node": ">=0.8.0"
+            }
+        },
+        "node_modules/@azure/msal-node": {
+            "version": "2.6.6",
+            "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-2.6.6.tgz",
+            "integrity": "sha512-j+1hW81ccglIYWukXufzRA4O71BCmpbmCO66ECDyE9FuPno6SjiR+K+mIk4tg6aQ7/UO2QA/EnRmT6YN0EF1Hw==",
+            "dependencies": {
+                "@azure/msal-common": "14.8.1",
+                "jsonwebtoken": "^9.0.0",
+                "uuid": "^8.3.0"
+            },
+            "engines": {
+                "node": ">=16"
+            }
+        },
+        "node_modules/@azure/msal-node/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/@babel/runtime": {
+            "version": "7.24.4",
+            "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.4.tgz",
+            "integrity": "sha512-dkxf7+hn8mFBwKjs9bvBlArzLVxVbS8usaPUDd5p2a9JCL9tB8OaOVN1isD4+Xyk4ns89/xeOmbQvgdK7IIVdA==",
+            "dependencies": {
+                "regenerator-runtime": "^0.14.0"
+            },
+            "engines": {
+                "node": ">=6.9.0"
+            }
+        },
+        "node_modules/@microsoft/microsoft-graph-client": {
+            "version": "3.0.7",
+            "resolved": "https://registry.npmjs.org/@microsoft/microsoft-graph-client/-/microsoft-graph-client-3.0.7.tgz",
+            "integrity": "sha512-/AazAV/F+HK4LIywF9C+NYHcJo038zEnWkteilcxC1FM/uK/4NVGDKGrxx7nNq1ybspAroRKT4I1FHfxQzxkUw==",
+            "dependencies": {
+                "@babel/runtime": "^7.12.5",
+                "tslib": "^2.2.0"
+            },
+            "engines": {
+                "node": ">=12.0.0"
+            },
+            "peerDependenciesMeta": {
+                "@azure/identity": {
+                    "optional": true
+                },
+                "@azure/msal-browser": {
+                    "optional": true
+                },
+                "buffer": {
+                    "optional": true
+                },
+                "stream-browserify": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@microsoft/teams-js": {
+            "version": "2.21.0",
+            "resolved": "https://registry.npmjs.org/@microsoft/teams-js/-/teams-js-2.21.0.tgz",
+            "integrity": "sha512-Q27kpuaqQZsBzHfBTudNLLraMfIn49PZuVXZo1TRMmo1AmJ+4BZ50q4VpiCe4r2Kk5xdbI3ZeTkO14Myh0U2kQ==",
+            "dependencies": {
+                "debug": "^4.3.3"
+            }
+        },
+        "node_modules/@microsoft/teams-js/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/@microsoft/teams-js/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/@netflix/nerror": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/@netflix/nerror/-/nerror-1.1.3.tgz",
+            "integrity": "sha512-b+MGNyP9/LXkapreJzNUzcvuzZslj/RGgdVVJ16P2wSlYatfLycPObImqVJSmNAdyeShvNeM/pl3sVZsObFueg==",
+            "dependencies": {
+                "assert-plus": "^1.0.0",
+                "extsprintf": "^1.4.0",
+                "lodash": "^4.17.15"
+            }
+        },
+        "node_modules/@netflix/nerror/node_modules/extsprintf": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz",
+            "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==",
+            "engines": [
+                "node >=0.6.0"
+            ]
+        },
+        "node_modules/abbrev": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+            "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+            "dev": true
+        },
+        "node_modules/abort-controller": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+            "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+            "dependencies": {
+                "event-target-shim": "^5.0.0"
+            },
+            "engines": {
+                "node": ">=6.5"
+            }
+        },
+        "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/agent-base": {
+            "version": "7.1.1",
+            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
+            "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
+            "dependencies": {
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/agent-base/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/agent-base/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/anymatch": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+            "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+            "dev": true,
+            "dependencies": {
+                "normalize-path": "^3.0.0",
+                "picomatch": "^2.0.4"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "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/asn1": {
+            "version": "0.2.6",
+            "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
+            "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
+            "dependencies": {
+                "safer-buffer": "~2.1.0"
+            }
+        },
+        "node_modules/assert-plus": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+            "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
+            "engines": {
+                "node": ">=0.8"
+            }
+        },
+        "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/atomic-sleep": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
+            "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
+            "engines": {
+                "node": ">=8.0.0"
+            }
+        },
+        "node_modules/axios": {
+            "version": "1.6.8",
+            "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz",
+            "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==",
+            "dependencies": {
+                "follow-redirects": "^1.15.6",
+                "form-data": "^4.0.0",
+                "proxy-from-env": "^1.1.0"
+            }
+        },
+        "node_modules/balanced-match": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+            "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+            "dev": true
+        },
+        "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/bcrypt-pbkdf": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+            "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
+            "dependencies": {
+                "tweetnacl": "^0.14.3"
+            }
+        },
+        "node_modules/binary-extensions": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+            "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "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/body-parser/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/body-parser/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+        },
+        "node_modules/body-parser/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/brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "dependencies": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "node_modules/braces": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+            "dev": true,
+            "dependencies": {
+                "fill-range": "^7.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/buffer": {
+            "version": "6.0.3",
+            "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+            "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+            "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.2.1"
+            }
+        },
+        "node_modules/buffer-equal-constant-time": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+            "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
+        },
+        "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.7",
+            "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+            "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+            "dependencies": {
+                "es-define-property": "^1.0.0",
+                "es-errors": "^1.3.0",
+                "function-bind": "^1.1.2",
+                "get-intrinsic": "^1.2.4",
+                "set-function-length": "^1.2.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/chokidar": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+            "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+            "dev": true,
+            "dependencies": {
+                "anymatch": "~3.1.2",
+                "braces": "~3.0.2",
+                "glob-parent": "~5.1.2",
+                "is-binary-path": "~2.1.0",
+                "is-glob": "~4.0.1",
+                "normalize-path": "~3.0.0",
+                "readdirp": "~3.6.0"
+            },
+            "engines": {
+                "node": ">= 8.10.0"
+            },
+            "funding": {
+                "url": "https://paulmillr.com/funding/"
+            },
+            "optionalDependencies": {
+                "fsevents": "~2.3.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/commander": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+            "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+            "dev": true,
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/concat-map": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+            "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+            "dev": true
+        },
+        "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.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-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-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.2",
+            "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+            "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
+        },
+        "node_modules/cors": {
+            "version": "2.8.5",
+            "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+            "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+            "dependencies": {
+                "object-assign": "^4",
+                "vary": "^1"
+            },
+            "engines": {
+                "node": ">= 0.10"
+            }
+        },
+        "node_modules/cross-spawn": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+            "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+            "dev": true,
+            "dependencies": {
+                "path-key": "^3.1.0",
+                "shebang-command": "^2.0.0",
+                "which": "^2.0.1"
+            },
+            "engines": {
+                "node": ">= 8"
+            }
+        },
+        "node_modules/csv": {
+            "version": "6.3.8",
+            "resolved": "https://registry.npmjs.org/csv/-/csv-6.3.8.tgz",
+            "integrity": "sha512-gRh3yiT9bHBA5ka2yOpyFqAVu/ZpwWzajMUR/es0ljevAE88WyHBuMUy7jzd2o5j6LYQesEO/AyhbQ9BhbDXUA==",
+            "dependencies": {
+                "csv-generate": "^4.4.0",
+                "csv-parse": "^5.5.5",
+                "csv-stringify": "^6.4.6",
+                "stream-transform": "^3.3.1"
+            },
+            "engines": {
+                "node": ">= 0.1.90"
+            }
+        },
+        "node_modules/csv-generate": {
+            "version": "4.4.0",
+            "resolved": "https://registry.npmjs.org/csv-generate/-/csv-generate-4.4.0.tgz",
+            "integrity": "sha512-geM01acNPZ0wr4/9sKev5fCzFG/tsc/NbuFWrhLc47M1zQyUdEJH65+cxTLIVafEwhBjIYwQ7fdOL9roBqVltQ=="
+        },
+        "node_modules/csv-parse": {
+            "version": "5.5.5",
+            "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-5.5.5.tgz",
+            "integrity": "sha512-erCk7tyU3yLWAhk6wvKxnyPtftuy/6Ak622gOO7BCJ05+TYffnPCJF905wmOQm+BpkX54OdAl8pveJwUdpnCXQ=="
+        },
+        "node_modules/csv-stringify": {
+            "version": "6.4.6",
+            "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz",
+            "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag=="
+        },
+        "node_modules/dashdash": {
+            "version": "1.14.1",
+            "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+            "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
+            "dependencies": {
+                "assert-plus": "^1.0.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/debug": {
+            "version": "3.2.7",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+            "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+            "dev": true,
+            "dependencies": {
+                "ms": "^2.1.1"
+            }
+        },
+        "node_modules/define-data-property": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+            "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+            "dependencies": {
+                "es-define-property": "^1.0.0",
+                "es-errors": "^1.3.0",
+                "gopd": "^1.0.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/define-lazy-prop": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
+            "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "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-node": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+            "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="
+        },
+        "node_modules/dote": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/dote/-/dote-1.1.0.tgz",
+            "integrity": "sha512-FgSIBaxINx0eL06MVKfOLuONBoL41vCQ5GM+MBBnYv2w2fvvgZGuqJGjU5cO3SNItXDyHmtiFG8nq+UITekDVQ==",
+            "dependencies": {
+                "readline-sync": "^1.4.10"
+            },
+            "bin": {
+                "doit": "index.js"
+            }
+        },
+        "node_modules/dotenv": {
+            "version": "16.4.5",
+            "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+            "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+            "engines": {
+                "node": ">=12"
+            },
+            "funding": {
+                "url": "https://dotenvx.com"
+            }
+        },
+        "node_modules/dtrace-provider": {
+            "version": "0.8.8",
+            "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
+            "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
+            "hasInstallScript": true,
+            "optional": true,
+            "dependencies": {
+                "nan": "^2.14.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/ecc-jsbn": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+            "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
+            "dependencies": {
+                "jsbn": "~0.1.0",
+                "safer-buffer": "^2.1.0"
+            }
+        },
+        "node_modules/ecdsa-sig-formatter": {
+            "version": "1.0.11",
+            "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+            "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+            "dependencies": {
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "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/env": {
+            "version": "0.0.2",
+            "resolved": "https://registry.npmjs.org/env/-/env-0.0.2.tgz",
+            "integrity": "sha512-yP8LfjO4ughSHD/3HgLPinWzexmaOGvRfs2TFx0SZhOm7j1xPi9evjuGcLiNVHIGLmcsgMak4eDbBzlYqGIVxw==",
+            "engines": {
+                "node": ">= 0.5.9"
+            }
+        },
+        "node_modules/env-cmd": {
+            "version": "10.1.0",
+            "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz",
+            "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==",
+            "dev": true,
+            "dependencies": {
+                "commander": "^4.0.0",
+                "cross-spawn": "^7.0.0"
+            },
+            "bin": {
+                "env-cmd": "bin/env-cmd.js"
+            },
+            "engines": {
+                "node": ">=8.0.0"
+            }
+        },
+        "node_modules/es-define-property": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+            "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+            "dependencies": {
+                "get-intrinsic": "^1.2.4"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/es-errors": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+            "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "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/escape-regexp-component": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/escape-regexp-component/-/escape-regexp-component-1.0.2.tgz",
+            "integrity": "sha512-B0yxafj1D1ZTNEHkFoQxz4iboZSfaZHhaNhIug7GcUCL4ZUrVSJZTmWUAkPOFaYDfi3RNT9XM082TuGE6jpmiQ=="
+        },
+        "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/event-target-shim": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+            "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/events": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
+            "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
+            "engines": {
+                "node": ">=0.8.x"
+            }
+        },
+        "node_modules/ewma": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/ewma/-/ewma-2.0.1.tgz",
+            "integrity": "sha512-MYYK17A76cuuyvkR7MnqLW4iFYPEi5Isl2qb8rXiWpLiwFS9dxW/rncuNnjjgSENuVqZQkIuR4+DChVL4g1lnw==",
+            "dependencies": {
+                "assert-plus": "^1.0.0"
+            }
+        },
+        "node_modules/express": {
+            "version": "4.19.2",
+            "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
+            "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
+            "dependencies": {
+                "accepts": "~1.3.8",
+                "array-flatten": "1.1.1",
+                "body-parser": "1.20.2",
+                "content-disposition": "0.5.4",
+                "content-type": "~1.0.4",
+                "cookie": "0.6.0",
+                "cookie-signature": "1.0.6",
+                "debug": "2.6.9",
+                "depd": "2.0.0",
+                "encodeurl": "~1.0.2",
+                "escape-html": "~1.0.3",
+                "etag": "~1.8.1",
+                "finalhandler": "1.2.0",
+                "fresh": "0.5.2",
+                "http-errors": "2.0.0",
+                "merge-descriptors": "1.0.1",
+                "methods": "~1.1.2",
+                "on-finished": "2.4.1",
+                "parseurl": "~1.3.3",
+                "path-to-regexp": "0.1.7",
+                "proxy-addr": "~2.0.7",
+                "qs": "6.11.0",
+                "range-parser": "~1.2.1",
+                "safe-buffer": "5.2.1",
+                "send": "0.18.0",
+                "serve-static": "1.15.0",
+                "setprototypeof": "1.2.0",
+                "statuses": "2.0.1",
+                "type-is": "~1.6.18",
+                "utils-merge": "1.0.1",
+                "vary": "~1.1.2"
+            },
+            "engines": {
+                "node": ">= 0.10.0"
+            }
+        },
+        "node_modules/express-session": {
+            "version": "1.18.0",
+            "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
+            "integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==",
+            "dependencies": {
+                "cookie": "0.6.0",
+                "cookie-signature": "1.0.7",
+                "debug": "2.6.9",
+                "depd": "~2.0.0",
+                "on-headers": "~1.0.2",
+                "parseurl": "~1.3.3",
+                "safe-buffer": "5.2.1",
+                "uid-safe": "~2.1.5"
+            },
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/express-session/node_modules/cookie": {
+            "version": "0.6.0",
+            "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+            "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/express-session/node_modules/cookie-signature": {
+            "version": "1.0.7",
+            "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+            "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
+        },
+        "node_modules/express-session/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/express-session/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+        },
+        "node_modules/express/node_modules/cookie": {
+            "version": "0.6.0",
+            "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
+            "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
+            "engines": {
+                "node": ">= 0.6"
+            }
+        },
+        "node_modules/express/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/express/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+        },
+        "node_modules/express/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/extsprintf": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+            "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==",
+            "engines": [
+                "node >=0.6.0"
+            ]
+        },
+        "node_modules/fast-decode-uri-component": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz",
+            "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg=="
+        },
+        "node_modules/fast-deep-equal": {
+            "version": "3.1.3",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+            "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+        },
+        "node_modules/fast-querystring": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz",
+            "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==",
+            "dependencies": {
+                "fast-decode-uri-component": "^1.0.1"
+            }
+        },
+        "node_modules/fast-redact": {
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz",
+            "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==",
+            "engines": {
+                "node": ">=6"
+            }
+        },
+        "node_modules/fill-range": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+            "dev": true,
+            "dependencies": {
+                "to-regex-range": "^5.0.1"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "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/finalhandler/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/finalhandler/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+        },
+        "node_modules/find-my-way": {
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-7.7.0.tgz",
+            "integrity": "sha512-+SrHpvQ52Q6W9f3wJoJBbAQULJuNEEQwBvlvYwACDhBTLOTMiQ0HYWh4+vC3OivGP2ENcTI1oKlFA2OepJNjhQ==",
+            "dependencies": {
+                "fast-deep-equal": "^3.1.3",
+                "fast-querystring": "^1.0.0",
+                "safe-regex2": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=14"
+            }
+        },
+        "node_modules/follow-redirects": {
+            "version": "1.15.6",
+            "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+            "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
+            "funding": [
+                {
+                    "type": "individual",
+                    "url": "https://github.com/sponsors/RubenVerborgh"
+                }
+            ],
+            "engines": {
+                "node": ">=4.0"
+            },
+            "peerDependenciesMeta": {
+                "debug": {
+                    "optional": true
+                }
+            }
+        },
+        "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/formidable": {
+            "version": "1.2.6",
+            "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz",
+            "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==",
+            "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau",
+            "funding": {
+                "url": "https://ko-fi.com/tunnckoCore/commissions"
+            }
+        },
+        "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": {
+            "version": "0.0.1-security",
+            "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
+            "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="
+        },
+        "node_modules/fsevents": {
+            "version": "2.3.3",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+            "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+            "dev": true,
+            "hasInstallScript": true,
+            "optional": true,
+            "os": [
+                "darwin"
+            ],
+            "engines": {
+                "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+            }
+        },
+        "node_modules/function-bind": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+            "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/get-intrinsic": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+            "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+            "dependencies": {
+                "es-errors": "^1.3.0",
+                "function-bind": "^1.1.2",
+                "has-proto": "^1.0.1",
+                "has-symbols": "^1.0.3",
+                "hasown": "^2.0.0"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/getpass": {
+            "version": "0.1.7",
+            "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+            "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
+            "dependencies": {
+                "assert-plus": "^1.0.0"
+            }
+        },
+        "node_modules/glob-parent": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+            "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+            "dev": true,
+            "dependencies": {
+                "is-glob": "^4.0.1"
+            },
+            "engines": {
+                "node": ">= 6"
+            }
+        },
+        "node_modules/gopd": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+            "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+            "dependencies": {
+                "get-intrinsic": "^1.1.3"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/handle-thing": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz",
+            "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg=="
+        },
+        "node_modules/has-flag": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+            "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+            "dev": true,
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/has-property-descriptors": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+            "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+            "dependencies": {
+                "es-define-property": "^1.0.0"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/has-proto": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+            "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+            "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/hasown": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+            "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+            "dependencies": {
+                "function-bind": "^1.1.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/hpack.js": {
+            "version": "2.1.6",
+            "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
+            "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==",
+            "dependencies": {
+                "inherits": "^2.0.1",
+                "obuf": "^1.0.0",
+                "readable-stream": "^2.0.1",
+                "wbuf": "^1.1.0"
+            }
+        },
+        "node_modules/hpack.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/hpack.js/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/hpack.js/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/http-deceiver": {
+            "version": "1.2.7",
+            "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz",
+            "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw=="
+        },
+        "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/http-proxy-agent": {
+            "version": "7.0.2",
+            "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+            "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+            "dependencies": {
+                "agent-base": "^7.1.0",
+                "debug": "^4.3.4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/http-proxy-agent/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/http-proxy-agent/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/http-signature": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
+            "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
+            "dependencies": {
+                "assert-plus": "^1.0.0",
+                "jsprim": "^2.0.2",
+                "sshpk": "^1.18.0"
+            },
+            "engines": {
+                "node": ">=0.10"
+            }
+        },
+        "node_modules/https-proxy-agent": {
+            "version": "7.0.4",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz",
+            "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==",
+            "dependencies": {
+                "agent-base": "^7.0.2",
+                "debug": "4"
+            },
+            "engines": {
+                "node": ">= 14"
+            }
+        },
+        "node_modules/https-proxy-agent/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/https-proxy-agent/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "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/ignore-by-default": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+            "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+            "dev": true
+        },
+        "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/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-binary-path": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+            "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+            "dev": true,
+            "dependencies": {
+                "binary-extensions": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/is-docker": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+            "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+            "bin": {
+                "is-docker": "cli.js"
+            },
+            "engines": {
+                "node": ">=8"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/sindresorhus"
+            }
+        },
+        "node_modules/is-extglob": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+            "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/is-glob": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+            "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+            "dev": true,
+            "dependencies": {
+                "is-extglob": "^2.1.1"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "node_modules/is-number": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.12.0"
+            }
+        },
+        "node_modules/is-wsl": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+            "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+            "dependencies": {
+                "is-docker": "^2.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "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/isexe": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+            "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+            "dev": true
+        },
+        "node_modules/jsbn": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+            "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="
+        },
+        "node_modules/json-schema": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+            "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+        },
+        "node_modules/jsonwebtoken": {
+            "version": "9.0.2",
+            "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
+            "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
+            "dependencies": {
+                "jws": "^3.2.2",
+                "lodash.includes": "^4.3.0",
+                "lodash.isboolean": "^3.0.3",
+                "lodash.isinteger": "^4.0.4",
+                "lodash.isnumber": "^3.0.3",
+                "lodash.isplainobject": "^4.0.6",
+                "lodash.isstring": "^4.0.1",
+                "lodash.once": "^4.0.0",
+                "ms": "^2.1.1",
+                "semver": "^7.5.4"
+            },
+            "engines": {
+                "node": ">=12",
+                "npm": ">=6"
+            }
+        },
+        "node_modules/jsonwebtoken/node_modules/jwa": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
+            "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
+            "dependencies": {
+                "buffer-equal-constant-time": "1.0.1",
+                "ecdsa-sig-formatter": "1.0.11",
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "node_modules/jsonwebtoken/node_modules/jws": {
+            "version": "3.2.2",
+            "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
+            "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
+            "dependencies": {
+                "jwa": "^1.4.1",
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "node_modules/jsonwebtoken/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/jsonwebtoken/node_modules/semver": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+            "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/jsprim": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz",
+            "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==",
+            "engines": [
+                "node >=0.6.0"
+            ],
+            "dependencies": {
+                "assert-plus": "1.0.0",
+                "extsprintf": "1.3.0",
+                "json-schema": "0.4.0",
+                "verror": "1.10.0"
+            }
+        },
+        "node_modules/jwa": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz",
+            "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==",
+            "dependencies": {
+                "buffer-equal-constant-time": "1.0.1",
+                "ecdsa-sig-formatter": "1.0.11",
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "node_modules/jws": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
+            "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
+            "dependencies": {
+                "jwa": "^2.0.0",
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "node_modules/jwt-decode": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+            "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+            "engines": {
+                "node": ">=18"
+            }
+        },
+        "node_modules/lodash": {
+            "version": "4.17.21",
+            "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+            "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+        },
+        "node_modules/lodash.includes": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+            "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
+        },
+        "node_modules/lodash.isboolean": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+            "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
+        },
+        "node_modules/lodash.isinteger": {
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+            "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
+        },
+        "node_modules/lodash.isnumber": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+            "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
+        },
+        "node_modules/lodash.isplainobject": {
+            "version": "4.0.6",
+            "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+            "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
+        },
+        "node_modules/lodash.isstring": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+            "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
+        },
+        "node_modules/lodash.once": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+            "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
+        },
+        "node_modules/lru-cache": {
+            "version": "7.18.3",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+            "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+            "engines": {
+                "node": ">=12"
+            }
+        },
+        "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": "3.0.0",
+            "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
+            "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==",
+            "bin": {
+                "mime": "cli.js"
+            },
+            "engines": {
+                "node": ">=10.0.0"
+            }
+        },
+        "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/minimalistic-assert": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+            "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
+        },
+        "node_modules/minimatch": {
+            "version": "3.1.2",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+            "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+            "dev": true,
+            "dependencies": {
+                "brace-expansion": "^1.1.7"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "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/nan": {
+            "version": "2.19.0",
+            "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
+            "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
+            "optional": true
+        },
+        "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/nodemon": {
+            "version": "2.0.22",
+            "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.22.tgz",
+            "integrity": "sha512-B8YqaKMmyuCO7BowF1Z1/mkPqLk6cs/l63Ojtd6otKjMx47Dq1utxfRxcavH1I7VSaL8n5BUaoutadnsX3AAVQ==",
+            "dev": true,
+            "dependencies": {
+                "chokidar": "^3.5.2",
+                "debug": "^3.2.7",
+                "ignore-by-default": "^1.0.1",
+                "minimatch": "^3.1.2",
+                "pstree.remy": "^1.1.8",
+                "semver": "^5.7.1",
+                "simple-update-notifier": "^1.0.7",
+                "supports-color": "^5.5.0",
+                "touch": "^3.1.0",
+                "undefsafe": "^2.0.5"
+            },
+            "bin": {
+                "nodemon": "bin/nodemon.js"
+            },
+            "engines": {
+                "node": ">=8.10.0"
+            },
+            "funding": {
+                "type": "opencollective",
+                "url": "https://opencollective.com/nodemon"
+            }
+        },
+        "node_modules/nopt": {
+            "version": "1.0.10",
+            "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+            "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==",
+            "dev": true,
+            "dependencies": {
+                "abbrev": "1"
+            },
+            "bin": {
+                "nopt": "bin/nopt.js"
+            },
+            "engines": {
+                "node": "*"
+            }
+        },
+        "node_modules/normalize-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+            "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+            "dev": true,
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "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.13.1",
+            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
+            "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/obuf": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+            "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="
+        },
+        "node_modules/on-exit-leak-free": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
+            "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
+            "engines": {
+                "node": ">=14.0.0"
+            }
+        },
+        "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/on-headers": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
+            "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
+            "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/open": {
+            "version": "8.4.2",
+            "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
+            "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
+            "dependencies": {
+                "define-lazy-prop": "^2.0.0",
+                "is-docker": "^2.1.1",
+                "is-wsl": "^2.2.0"
+            },
+            "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": {
+            "version": "0.12.7",
+            "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
+            "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
+            "dependencies": {
+                "process": "^0.11.1",
+                "util": "^0.10.3"
+            }
+        },
+        "node_modules/path-key": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+            "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+            "dev": true,
+            "engines": {
+                "node": ">=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/picomatch": {
+            "version": "2.3.1",
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+            "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+            "dev": true,
+            "engines": {
+                "node": ">=8.6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/jonschlinkert"
+            }
+        },
+        "node_modules/pidusage": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/pidusage/-/pidusage-3.0.2.tgz",
+            "integrity": "sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w==",
+            "dependencies": {
+                "safe-buffer": "^5.2.1"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/pino": {
+            "version": "8.20.0",
+            "resolved": "https://registry.npmjs.org/pino/-/pino-8.20.0.tgz",
+            "integrity": "sha512-uhIfMj5TVp+WynVASaVEJFTncTUe4dHBq6CWplu/vBgvGHhvBvQfxz+vcOrnnBQdORH3izaGEurLfNlq3YxdFQ==",
+            "dependencies": {
+                "atomic-sleep": "^1.0.0",
+                "fast-redact": "^3.1.1",
+                "on-exit-leak-free": "^2.1.0",
+                "pino-abstract-transport": "^1.1.0",
+                "pino-std-serializers": "^6.0.0",
+                "process-warning": "^3.0.0",
+                "quick-format-unescaped": "^4.0.3",
+                "real-require": "^0.2.0",
+                "safe-stable-stringify": "^2.3.1",
+                "sonic-boom": "^3.7.0",
+                "thread-stream": "^2.0.0"
+            },
+            "bin": {
+                "pino": "bin.js"
+            }
+        },
+        "node_modules/pino-abstract-transport": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz",
+            "integrity": "sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA==",
+            "dependencies": {
+                "readable-stream": "^4.0.0",
+                "split2": "^4.0.0"
+            }
+        },
+        "node_modules/pino-std-serializers": {
+            "version": "6.2.2",
+            "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz",
+            "integrity": "sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA=="
+        },
+        "node_modules/process": {
+            "version": "0.11.10",
+            "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+            "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
+            "engines": {
+                "node": ">= 0.6.0"
+            }
+        },
+        "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/process-warning": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-3.0.0.tgz",
+            "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="
+        },
+        "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/proxy-from-env": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+            "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+        },
+        "node_modules/pstree.remy": {
+            "version": "1.1.8",
+            "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+            "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+            "dev": true
+        },
+        "node_modules/qs": {
+            "version": "6.12.0",
+            "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz",
+            "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==",
+            "dependencies": {
+                "side-channel": "^1.0.6"
+            },
+            "engines": {
+                "node": ">=0.6"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/quick-format-unescaped": {
+            "version": "4.0.4",
+            "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
+            "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg=="
+        },
+        "node_modules/random-bytes": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
+            "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
+        "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/readable-stream": {
+            "version": "4.5.2",
+            "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
+            "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
+            "dependencies": {
+                "abort-controller": "^3.0.0",
+                "buffer": "^6.0.3",
+                "events": "^3.3.0",
+                "process": "^0.11.10",
+                "string_decoder": "^1.3.0"
+            },
+            "engines": {
+                "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+            }
+        },
+        "node_modules/readdirp": {
+            "version": "3.6.0",
+            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+            "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+            "dev": true,
+            "dependencies": {
+                "picomatch": "^2.2.1"
+            },
+            "engines": {
+                "node": ">=8.10.0"
+            }
+        },
+        "node_modules/readline-sync": {
+            "version": "1.4.10",
+            "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
+            "integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw==",
+            "engines": {
+                "node": ">= 0.8.0"
+            }
+        },
+        "node_modules/real-require": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
+            "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
+            "engines": {
+                "node": ">= 12.13.0"
+            }
+        },
+        "node_modules/regenerator-runtime": {
+            "version": "0.14.1",
+            "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
+            "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="
+        },
+        "node_modules/restify": {
+            "version": "11.1.0",
+            "resolved": "https://registry.npmjs.org/restify/-/restify-11.1.0.tgz",
+            "integrity": "sha512-ng7uBlj4wpIpshhAjNNSd6JG5Eg32+zgync2gG8OlF4e2xzIflZo54GJ/qLs765OtQaVU+uJPcNOL5Atm2F/dg==",
+            "dependencies": {
+                "assert-plus": "^1.0.0",
+                "csv": "^6.2.2",
+                "escape-regexp-component": "^1.0.2",
+                "ewma": "^2.0.1",
+                "find-my-way": "^7.2.0",
+                "formidable": "^1.2.1",
+                "http-signature": "^1.3.6",
+                "lodash": "^4.17.11",
+                "lru-cache": "^7.14.1",
+                "mime": "^3.0.0",
+                "negotiator": "^0.6.2",
+                "once": "^1.4.0",
+                "pidusage": "^3.0.2",
+                "pino": "^8.7.0",
+                "qs": "^6.7.0",
+                "restify-errors": "^8.0.2",
+                "semver": "^7.3.8",
+                "send": "^0.18.0",
+                "spdy": "^4.0.0",
+                "uuid": "^9.0.0",
+                "vasync": "^2.2.0"
+            },
+            "bin": {
+                "report-latency": "bin/report-latency"
+            },
+            "engines": {
+                "node": ">=10.0.0"
+            },
+            "optionalDependencies": {
+                "dtrace-provider": "~0.8"
+            }
+        },
+        "node_modules/restify-errors": {
+            "version": "8.0.2",
+            "resolved": "https://registry.npmjs.org/restify-errors/-/restify-errors-8.0.2.tgz",
+            "integrity": "sha512-UsXUVQo7M26xoQzeUcZQ0+H8L2t9DGzrXcAgR3WB/1vnbl+UdI4tZ1PqYsN+sS5WnqHKZ0Xy9w0CKf83bbrwYA==",
+            "dependencies": {
+                "@netflix/nerror": "^1.0.0",
+                "assert-plus": "^1.0.0",
+                "lodash": "^4.17.15"
+            },
+            "optionalDependencies": {
+                "safe-json-stringify": "^1.0.4"
+            }
+        },
+        "node_modules/restify/node_modules/semver": {
+            "version": "7.6.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+            "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+            "dependencies": {
+                "lru-cache": "^6.0.0"
+            },
+            "bin": {
+                "semver": "bin/semver.js"
+            },
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "node_modules/restify/node_modules/semver/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/ret": {
+            "version": "0.2.2",
+            "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz",
+            "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==",
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "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/safe-json-stringify": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
+            "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
+            "optional": true
+        },
+        "node_modules/safe-regex2": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-2.0.0.tgz",
+            "integrity": "sha512-PaUSFsUaNNuKwkBijoAPHAK6/eM6VirvyPWlZ7BAQy4D+hCvh4B6lIG+nPdhbFfIbP+gTGBcrdsOaUs0F+ZBOQ==",
+            "dependencies": {
+                "ret": "~0.2.0"
+            }
+        },
+        "node_modules/safe-stable-stringify": {
+            "version": "2.4.3",
+            "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz",
+            "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==",
+            "engines": {
+                "node": ">=10"
+            }
+        },
+        "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/select-hose": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
+            "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg=="
+        },
+        "node_modules/semver": {
+            "version": "5.7.2",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+            "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+            "dev": true,
+            "bin": {
+                "semver": "bin/semver"
+            }
+        },
+        "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/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/send/node_modules/debug/node_modules/ms": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+            "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+        },
+        "node_modules/send/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/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/set-function-length": {
+            "version": "1.2.2",
+            "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+            "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+            "dependencies": {
+                "define-data-property": "^1.1.4",
+                "es-errors": "^1.3.0",
+                "function-bind": "^1.1.2",
+                "get-intrinsic": "^1.2.4",
+                "gopd": "^1.0.1",
+                "has-property-descriptors": "^1.0.2"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            }
+        },
+        "node_modules/setprototypeof": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+            "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+        },
+        "node_modules/shebang-command": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+            "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+            "dev": true,
+            "dependencies": {
+                "shebang-regex": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/shebang-regex": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+            "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+            "dev": true,
+            "engines": {
+                "node": ">=8"
+            }
+        },
+        "node_modules/side-channel": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+            "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+            "dependencies": {
+                "call-bind": "^1.0.7",
+                "es-errors": "^1.3.0",
+                "get-intrinsic": "^1.2.4",
+                "object-inspect": "^1.13.1"
+            },
+            "engines": {
+                "node": ">= 0.4"
+            },
+            "funding": {
+                "url": "https://github.com/sponsors/ljharb"
+            }
+        },
+        "node_modules/simple-update-notifier": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-1.1.0.tgz",
+            "integrity": "sha512-VpsrsJSUcJEseSbMHkrsrAVSdvVS5I96Qo1QAQ4FxQ9wXFcB+pjj7FB7/us9+GcgfW4ziHtYMc1J0PLczb55mg==",
+            "dev": true,
+            "dependencies": {
+                "semver": "~7.0.0"
+            },
+            "engines": {
+                "node": ">=8.10.0"
+            }
+        },
+        "node_modules/simple-update-notifier/node_modules/semver": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz",
+            "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==",
+            "dev": true,
+            "bin": {
+                "semver": "bin/semver.js"
+            }
+        },
+        "node_modules/sonic-boom": {
+            "version": "3.8.1",
+            "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-3.8.1.tgz",
+            "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==",
+            "dependencies": {
+                "atomic-sleep": "^1.0.0"
+            }
+        },
+        "node_modules/spdy": {
+            "version": "4.0.2",
+            "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
+            "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==",
+            "dependencies": {
+                "debug": "^4.1.0",
+                "handle-thing": "^2.0.0",
+                "http-deceiver": "^1.2.7",
+                "select-hose": "^2.0.0",
+                "spdy-transport": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=6.0.0"
+            }
+        },
+        "node_modules/spdy-transport": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz",
+            "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==",
+            "dependencies": {
+                "debug": "^4.1.0",
+                "detect-node": "^2.0.4",
+                "hpack.js": "^2.1.6",
+                "obuf": "^1.1.2",
+                "readable-stream": "^3.0.6",
+                "wbuf": "^1.7.3"
+            }
+        },
+        "node_modules/spdy-transport/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/spdy-transport/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/spdy-transport/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/spdy/node_modules/debug": {
+            "version": "4.3.4",
+            "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+            "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+            "dependencies": {
+                "ms": "2.1.2"
+            },
+            "engines": {
+                "node": ">=6.0"
+            },
+            "peerDependenciesMeta": {
+                "supports-color": {
+                    "optional": true
+                }
+            }
+        },
+        "node_modules/spdy/node_modules/ms": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+            "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+        },
+        "node_modules/split2": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+            "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+            "engines": {
+                "node": ">= 10.x"
+            }
+        },
+        "node_modules/sshpk": {
+            "version": "1.18.0",
+            "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
+            "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
+            "dependencies": {
+                "asn1": "~0.2.3",
+                "assert-plus": "^1.0.0",
+                "bcrypt-pbkdf": "^1.0.0",
+                "dashdash": "^1.12.0",
+                "ecc-jsbn": "~0.1.1",
+                "getpass": "^0.1.1",
+                "jsbn": "~0.1.0",
+                "safer-buffer": "^2.0.2",
+                "tweetnacl": "~0.14.0"
+            },
+            "bin": {
+                "sshpk-conv": "bin/sshpk-conv",
+                "sshpk-sign": "bin/sshpk-sign",
+                "sshpk-verify": "bin/sshpk-verify"
+            },
+            "engines": {
+                "node": ">=0.10.0"
+            }
+        },
+        "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/stoppable": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz",
+            "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==",
+            "engines": {
+                "node": ">=4",
+                "npm": ">=6"
+            }
+        },
+        "node_modules/stream-transform": {
+            "version": "3.3.1",
+            "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.3.1.tgz",
+            "integrity": "sha512-BL8pv9QL8Ikd11oZwlRDp1qYMhGR0i50zI9ltoijKGc4ubQWal/Rc4p6SYJp1TBOGpE0uAGchwbxOZ1ycwTuqQ=="
+        },
+        "node_modules/string_decoder": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+            "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+            "dependencies": {
+                "safe-buffer": "~5.2.0"
+            }
+        },
+        "node_modules/supports-color": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "dev": true,
+            "dependencies": {
+                "has-flag": "^3.0.0"
+            },
+            "engines": {
+                "node": ">=4"
+            }
+        },
+        "node_modules/thread-stream": {
+            "version": "2.4.1",
+            "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-2.4.1.tgz",
+            "integrity": "sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg==",
+            "dependencies": {
+                "real-require": "^0.2.0"
+            }
+        },
+        "node_modules/to-regex-range": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+            "dev": true,
+            "dependencies": {
+                "is-number": "^7.0.0"
+            },
+            "engines": {
+                "node": ">=8.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/touch": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
+            "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
+            "dev": true,
+            "dependencies": {
+                "nopt": "~1.0.10"
+            },
+            "bin": {
+                "nodetouch": "bin/nodetouch.js"
+            }
+        },
+        "node_modules/tslib": {
+            "version": "2.6.2",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+            "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+        },
+        "node_modules/tweetnacl": {
+            "version": "0.14.5",
+            "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+            "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="
+        },
+        "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/uid-safe": {
+            "version": "2.1.5",
+            "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
+            "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
+            "dependencies": {
+                "random-bytes": "~1.0.0"
+            },
+            "engines": {
+                "node": ">= 0.8"
+            }
+        },
+        "node_modules/undefsafe": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+            "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+            "dev": true
+        },
+        "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": {
+            "version": "0.10.4",
+            "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
+            "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
+            "dependencies": {
+                "inherits": "2.0.3"
+            }
+        },
+        "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/util/node_modules/inherits": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+            "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw=="
+        },
+        "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/vasync": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/vasync/-/vasync-2.2.1.tgz",
+            "integrity": "sha512-Hq72JaTpcTFdWiNA4Y22Amej2GH3BFmBaKPPlDZ4/oC8HNn2ISHLkFrJU4Ds8R3jcUi7oo5Y9jcMHKjES+N9wQ==",
+            "engines": [
+                "node >=0.6.0"
+            ],
+            "dependencies": {
+                "verror": "1.10.0"
+            }
+        },
+        "node_modules/verror": {
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+            "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==",
+            "engines": [
+                "node >=0.6.0"
+            ],
+            "dependencies": {
+                "assert-plus": "^1.0.0",
+                "core-util-is": "1.0.2",
+                "extsprintf": "^1.2.0"
+            }
+        },
+        "node_modules/wbuf": {
+            "version": "1.7.3",
+            "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz",
+            "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==",
+            "dependencies": {
+                "minimalistic-assert": "^1.0.0"
+            }
+        },
+        "node_modules/which": {
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+            "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+            "dev": true,
+            "dependencies": {
+                "isexe": "^2.0.0"
+            },
+            "bin": {
+                "node-which": "bin/node-which"
+            },
+            "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/yallist": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+            "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+        }
+    }
+}

+ 37 - 0
package.json

@@ -0,0 +1,37 @@
+{
+    "name": "share",
+    "version": "0.1.0",
+    "engines": {
+        "node": "16 || 18"
+    },
+    "private": true,
+    "dependencies": {
+        "@azure/communication-identity": "^1.3.1",
+        "@azure/identity": "^4.0.1",
+        "@azure/msal-node": "^2.6.6",
+        "@microsoft/microsoft-graph-client": "^3.0.7",
+        "@microsoft/teams-js": "^2.21.0",
+        "axios": "^1.6.8",
+        "cookie-parser": "^1.4.6",
+        "cors": "^2.8.5",
+        "dote": "^1.1.0",
+        "dotenv": "^16.4.5",
+        "env": "^0.0.2",
+        "express": "^4.19.2",
+        "express-session": "^1.18.0",
+        "fs": "^0.0.1-security",
+        "path": "^0.12.7",
+        "restify": "^11.1.0",
+        "send": "^0.18.0"
+    },
+    "devDependencies": {
+        "env-cmd": "^10.1.0",
+        "nodemon": "^2.0.21"
+    },
+    "scripts": {
+        "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run start",
+        "start": "nodemon --inspect=9239 --signal SIGINT src/app.js",
+        "test": "echo \"Error: no test specified\" && exit 1"
+    },
+    "homepage": "."
+}

+ 189 - 0
src/app.js

@@ -0,0 +1,189 @@
+const cookieParser = require('cookie-parser');
+const cors = require("cors");
+const fs = require("fs");
+const path = require("path");
+const express = require("express");
+const axios = require("axios");
+const SERVER_PORT = process.env.port || process.env.PORT || 3333;
+const authProvider = require('./auth/AuthProvider');
+const {fetch, updateFetch} = require('./fetch');
+const bodyParser = require('body-parser');
+const https = require('https');
+require('dotenv').config({ path: './env/.env.test' });
+const session = require('express-session');
+
+const serverApp = express();
+
+serverApp.use(session({
+  secret: process.env.EXPRESS_SESSION_SECRET,
+  resave: false,
+  saveUninitialized: true,
+  cookie: {
+      httpOnly: true,
+      secure: true, // set this to true on production
+      sameSite: 'none',
+      maxAge: 60 * 60 * 24 * 1000
+  }
+}));
+
+serverApp.set(express.json());
+serverApp.use(cookieParser());
+serverApp.use(express.urlencoded({ extended: false }));
+serverApp.use("/static",express.static(path.join(__dirname, 'static')));
+serverApp.use("/node_modules",express.static(path.join(__dirname, 'node_modules')));
+
+const options = {
+  key: process.env.SSL_KEY_FILE ? fs.readFileSync(process.env.SSL_KEY_FILE) : undefined,
+  cert: process.env.SSL_CRT_FILE ? fs.readFileSync(process.env.SSL_CRT_FILE) : undefined,
+};
+
+const server = https.createServer(options, serverApp);
+
+const corsOption = {
+  origin: "*",
+}
+
+
+serverApp.use(cors(corsOption));
+serverApp.use(bodyParser.json());
+
+server.listen(SERVER_PORT, function () {
+  console.log(`\n${serverApp.name} listening to ${SERVER_PORT}`);
+});
+
+serverApp.get("/tab", 
+  isAuthenticated, 
+  async function (req, res, next) {
+    res.sendFile(path.join(__dirname, "/views/hello.html"), 
+      { idTokenClaims: req.session.account.idTokenClaims }
+    );
+  }
+);
+
+function isAuthenticated(req, res, next) {
+  if (!req.session.isAuthenticated) {
+    return res.redirect('/auth/signin'); // redirect to sign-in route
+  }
+  next();
+}; 
+
+function isAccessToken(req, res, next) {
+  if (!req.session.accessToken) {
+    return authProvider.acquireToken({
+      scopes: ['.default'],
+      redirectUri: 'https://localhost:53000/redirect',
+      successRedirect: '/api-redirect'
+    })(req, res, next);
+  }
+  next();
+}
+
+serverApp.get("/auth/signin", authProvider.login({
+  scopes: ['.default'],
+  redirectUri: 'https://localhost:53000/redirect',
+  successRedirect: '/tab'
+}))
+
+serverApp.post("/redirect", authProvider.handleRedirect());
+
+serverApp.post("/api-get",   
+  isAuthenticated,
+  isAccessToken, 
+  async (req, res, next) => {
+    const uri = req.body.api_uri || req.session.apiUri;
+    let param = {};
+    if (req.session.param) {
+      param = req.session.param;
+    }
+    try {
+        const graphResponse = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0" + uri, req.session.accessToken, param);
+        res.json(graphResponse);
+    } catch (error) {
+        next(error);
+    }
+});
+
+serverApp.get("/api-redirect", 
+  //isAuthenticated,
+  async function (req, res, next) {
+      const uri = req.session.apiUri;
+      let param = {};
+      if (req.session.param) {
+        param = req.session.param;
+      }
+      try {
+          const graphResponse = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0" + uri, req.session.accessToken, param);
+          res.json(graphResponse);
+      } catch (error) {
+          next(error);
+      }
+})
+
+serverApp.get("/post-redirect", 
+  isAuthenticated,
+  async function (req, res, next) {
+    const uri = req.session.apiUri;
+    let param = {};
+      if (req.session.param) {
+        param = req.session.param;
+      }
+    try {
+        const graphResponse = await updateFetch(process.env.GRAPH_API_ENDPOINT + "v1.0" + uri, req.session.accessToken, param);
+        res.json(graphResponse);
+    } catch (error) {
+        next(error);
+    }
+  }
+)
+
+serverApp.post("/api-update", authProvider.acquireToken({
+  scopes: [],
+  redirectUri: 'https://localhost:53000/redirect',
+  successRedirect: '/post-redirect'
+}));
+
+serverApp.post("/api-post", authProvider.acquireToken({
+  scopes: ['.default'],
+  redirectUri: 'https://localhost:53000/redirect',
+  successRedirect: '/post-redirect'
+}));
+
+serverApp.post("/getGroupList", authProvider.acquireToken({
+  scopes: ['.default'],
+  redirectUri: 'https://localhost:53000/redirect',
+  successRedirect: '/group-redirect'
+}));
+
+serverApp.get("/group-redirect", 
+  isAuthenticated,
+  async function (req, res, next) {
+    // return;
+    try {
+        const oneDrive = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0/me/drive/root/children", req.session.accessToken);
+        const graphResponse = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0/me/joinedTeams", req.session.accessToken);
+        const sites = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0/sites/root", req.session.accessToken);
+        const teams = graphResponse.value;
+        const resultObj = {
+          oneDrive : oneDrive.value,
+          joinedTeams : {
+            teams : teams,
+            items : {},
+          },
+          sites : [],
+        }
+        if (teams && teams.length) {
+          for(let team of teams) {
+            const item = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0/groups/"+team.id+"/drive/items/root/children", req.session.accessToken);
+            const photo = await fetch(process.env.GRAPH_API_ENDPOINT + "v1.0/groups/"+team.id+"/photo", req.session.accessToken);
+            if (item && item.value) {
+              resultObj.joinedTeams.items[team.id] = item.value;
+              team.image = photo;
+            }
+          }
+        }
+        res.json(resultObj);
+    } catch (error) {
+        next(error);
+    }
+  }
+)

+ 272 - 0
src/auth/AuthProvider.js

@@ -0,0 +1,272 @@
+const msal = require('@azure/msal-node');
+const axios = require('axios');
+
+const { msalConfig } = require('../authConfig');
+
+class AuthProvider {
+    msalConfig;
+    cryptoProvider;
+
+    constructor(msalConfig) {
+        this.msalConfig = msalConfig
+        this.cryptoProvider = new msal.CryptoProvider();
+    };
+
+    login(options = {}) {
+        return async (req, res, next) => {
+            /**
+             * MSAL Node library allows you to pass your custom state as state parameter in the Request object.
+             * The state parameter can also be used to encode information of the app's state before redirect.
+             * You can pass the user's state in the app, such as the page or view they were on, as input to this parameter.
+             */
+            const state = this.cryptoProvider.base64Encode(
+                JSON.stringify({
+                    successRedirect: options.successRedirect || '/',
+                })
+            );
+            const authCodeUrlRequestParams = {
+                state: state,
+
+                /**
+                 * By default, MSAL Node will add OIDC scopes to the auth code url request. For more information, visit:
+                 * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
+                 */
+                scopes: options.scopes || [],
+                redirectUri: options.redirectUri,
+            };
+
+            const authCodeRequestParams = {
+                state: state,
+
+                /**
+                 * By default, MSAL Node will add OIDC scopes to the auth code request. For more information, visit:
+                 * https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#openid-connect-scopes
+                 */
+                scopes: options.scopes || [],
+                redirectUri: options.redirectUri,
+            };
+
+            /**
+             * If the current msal configuration does not have cloudDiscoveryMetadata or authorityMetadata, we will 
+             * make a request to the relevant endpoints to retrieve the metadata. This allows MSAL to avoid making 
+             * metadata discovery calls, thereby improving performance of token acquisition process. For more, see:
+             * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/performance.md
+             */
+            if (!this.msalConfig.auth.cloudDiscoveryMetadata || !this.msalConfig.auth.authorityMetadata) {
+
+                const [cloudDiscoveryMetadata, authorityMetadata] = await Promise.all([
+                    this.getCloudDiscoveryMetadata(this.msalConfig.auth.authority),
+                    this.getAuthorityMetadata(this.msalConfig.auth.authority)
+                ]);
+
+                this.msalConfig.auth.cloudDiscoveryMetadata = JSON.stringify(cloudDiscoveryMetadata);
+                this.msalConfig.auth.authorityMetadata = JSON.stringify(authorityMetadata);
+            }
+
+            const msalInstance = this.getMsalInstance(this.msalConfig);
+            // trigger the first leg of auth code flow
+            return this.redirectToAuthCodeUrl(
+                authCodeUrlRequestParams,
+                authCodeRequestParams,
+                msalInstance
+            )(req, res, next);
+        };
+    }
+
+    acquireToken(options = {}) {
+        return async (req, res, next) => {
+            try {
+                const msalInstance = this.getMsalInstance(this.msalConfig);
+
+                /**
+                 * If a token cache exists in the session, deserialize it and set it as the 
+                 * cache for the new MSAL CCA instance. For more, see: 
+                 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
+                 */
+                if (req.session.tokenCache) {
+                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
+                }
+                
+                const tokenResponse = await msalInstance.acquireTokenSilent({
+                    account: req.session.account,
+                    scopes: options.scopes || req.body.scopes || [],
+                });
+                /**
+                 * On successful token acquisition, write the updated token 
+                 * cache back to the session. For more, see: 
+                 * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/caching.md
+                 */
+                req.session.tokenCache = msalInstance.getTokenCache().serialize();
+                req.session.accessToken = tokenResponse.accessToken;
+                req.session.idToken = tokenResponse.idToken;
+                req.session.account = tokenResponse.account;
+                req.session.apiUri = req.body.api_uri;
+                req.session.param  = req.body.param;
+                res.redirect(options.successRedirect);
+            } catch (error) {
+                if (error instanceof msal.InteractionRequiredAuthError) {
+                    return this.login({
+                        scopes: options.scopes || [],
+                        redirectUri: options.redirectUri,
+                        successRedirect: options.successRedirect || '/',
+                    })(req, res, next);
+                }
+
+                next(error);
+            }
+        };
+    }
+
+    handleRedirect(options = {}) {
+        return async (req, res, next) => {
+
+            if (!req.body || !req.body.state) {
+                return next(new Error('Error: response not found'));
+            }
+            if (!req.session || !req.session.pkceCodes) {
+                return next(new Error('Error: session pkceCodes not found'));
+            }
+            const authCodeRequest = { 
+                ...req.session.authCodeRequest, 
+                code: req.body.code,
+                codeVerifier: req.session.pkceCodes.verifier,
+            };
+
+            try {
+                const msalInstance = this.getMsalInstance(this.msalConfig);
+
+                if (req.session.tokenCache) {
+                    msalInstance.getTokenCache().deserialize(req.session.tokenCache);
+                }
+
+                const tokenResponse = await msalInstance.acquireTokenByCode(authCodeRequest, req.body);
+
+                req.session.tokenCache = msalInstance.getTokenCache().serialize();
+                req.session.idToken = tokenResponse.idToken;
+                req.session.account = tokenResponse.account;
+                req.session.isAuthenticated = true;
+                const state = JSON.parse(this.cryptoProvider.base64Decode(req.body.state));
+                res.redirect(state.successRedirect);
+            } catch (error) {
+                next(error);
+            }
+        }
+    }
+
+    logout(options = {}) {
+        return (req, res, next) => {
+
+            /**
+             * Construct a logout URI and redirect the user to end the
+             * session with Azure AD. For more information, visit:
+             * https://docs.microsoft.com/azure/active-directory/develop/v2-protocols-oidc#send-a-sign-out-request
+             */
+            let logoutUri = `${this.msalConfig.auth.authority}/oauth2/v2.0/`;
+
+            if (options.postLogoutRedirectUri) {
+                logoutUri += `logout?post_logout_redirect_uri=${options.postLogoutRedirectUri}`;
+            }
+
+            req.session.destroy(() => {
+                res.redirect(logoutUri);
+            });
+        }
+    }
+
+    /**
+     * Instantiates a new MSAL ConfidentialClientApplication object
+     * @param msalConfig: MSAL Node Configuration object 
+     * @returns 
+     */
+    getMsalInstance(msalConfig) {
+        return new msal.ConfidentialClientApplication(msalConfig);
+    }
+
+
+    /**
+     * Prepares the auth code request parameters and initiates the first leg of auth code flow
+     * @param req: Express request object
+     * @param res: Express response object
+     * @param next: Express next function
+     * @param authCodeUrlRequestParams: parameters for requesting an auth code url
+     * @param authCodeRequestParams: parameters for requesting tokens using auth code
+     */
+    redirectToAuthCodeUrl(authCodeUrlRequestParams, authCodeRequestParams, msalInstance) {
+        return async (req, res, next) => {
+            // Generate PKCE Codes before starting the authorization flow
+            const { verifier, challenge } = await this.cryptoProvider.generatePkceCodes();
+           // Set generated PKCE codes and method as session vars
+            req.session.pkceCodes = {
+                challengeMethod: 'S256',
+                verifier: verifier,
+                challenge: challenge,
+            };
+
+            /**
+             * By manipulating the request objects below before each request, we can obtain
+             * auth artifacts with desired claims. For more information, visit:
+             * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationurlrequest
+             * https://azuread.github.io/microsoft-authentication-library-for-js/ref/modules/_azure_msal_node.html#authorizationcoderequest
+             **/
+            req.session.authCodeUrlRequest = {
+                ...authCodeUrlRequestParams,
+                responseMode: msal.ResponseMode.FORM_POST, // recommended for confidential clients
+                codeChallenge: req.session.pkceCodes.challenge,
+                codeChallengeMethod: req.session.pkceCodes.challengeMethod,
+            };
+
+            req.session.authCodeRequest = {
+                ...authCodeRequestParams,
+                code: '',
+            };
+            
+            try {
+                const authCodeUrlResponse = await msalInstance.getAuthCodeUrl(req.session.authCodeUrlRequest);
+
+                res.redirect(authCodeUrlResponse);
+            } catch (error) {
+                next(error);
+            }
+        };
+    }
+
+    /**
+     * Retrieves cloud discovery metadata from the /discovery/instance endpoint
+     * @returns 
+     */
+    async getCloudDiscoveryMetadata(authority) {
+        const endpoint = 'https://login.microsoftonline.com/common/discovery/instance';
+
+        try {
+            const response = await axios.get(endpoint, {
+                params: {
+                    'api-version': '1.1',
+                    'authorization_endpoint': `${authority}/oauth2/v2.0/authorize`
+                }
+            });
+
+            return await response.data;
+        } catch (error) {
+            throw error;
+        }
+    }
+
+    /**
+     * Retrieves oidc metadata from the openid endpoint
+     * @returns
+     */
+    async getAuthorityMetadata(authority) {
+        const endpoint = `${authority}/v2.0/.well-known/openid-configuration`;
+
+        try {
+            const response = await axios.get(endpoint);
+            return await response.data;
+        } catch (error) {
+            console.log(error);
+        }
+    }
+}
+
+const authProvider = new AuthProvider(msalConfig);
+
+module.exports = authProvider;

+ 40 - 0
src/authConfig.js

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+require('dotenv').config({ path: './env/.env.test' });
+
+/**
+ * Configuration object to be passed to MSAL instance on creation.
+ * For a full list of MSAL Node configuration parameters, visit:
+ * https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-node/docs/configuration.md
+ */
+
+const msalConfig = {
+    auth: {
+        clientId: process.env.CLIENT_ID, // 'Application (client) ID' of app registration in Azure portal - this value is a GUID
+        authority: process.env.CLOUD_INSTANCE + process.env.TENANT_ID, // Full directory URL, in the form of https://login.microsoftonline.com/<tenant>
+        clientSecret: process.env.CLIENT_SECRET // Client secret generated from the app registration in Azure portal
+    },
+    system: {
+        loggerOptions: {
+            loggerCallback(loglevel, message, containsPii) {
+                console.log(message);
+            },
+            piiLoggingEnabled: false,
+            logLevel: 3,
+        }
+    }
+}
+
+const REDIRECT_URI = process.env.REDIRECT_URI;
+const POST_LOGOUT_REDIRECT_URI = process.env.POST_LOGOUT_REDIRECT_URI;
+const GRAPH_ME_ENDPOINT = process.env.GRAPH_API_ENDPOINT + "v1.0/me";
+
+module.exports = {
+    msalConfig,
+    REDIRECT_URI,
+    POST_LOGOUT_REDIRECT_URI,
+    GRAPH_ME_ENDPOINT
+};

+ 60 - 0
src/fetch.js

@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+var axios = require('axios');
+
+/**
+ * Attaches a given access token to a MS Graph API call
+ * @param endpoint: REST API endpoint to call
+ * @param accessToken: raw access token string
+ */
+async function fetch(endpoint, accessToken, param) {
+    const options = {
+        headers: {
+            Authorization: `Bearer ${accessToken}`
+        }
+    };
+
+
+    if (!param) {
+        param = {};
+    }
+
+    console.log(`request made to ${endpoint} at: ` + new Date().toString());
+
+    try {
+        const response = await axios.get(endpoint, options, param);
+        return await response.data;
+    } catch (error) {
+        throw new Error(error);
+    }
+}
+
+async function updateFetch(endpoint, accessToken, params) {
+    const options = {
+        headers: {
+            Authorization: `Bearer ${accessToken}`
+        }
+    };
+
+    console.log(`request made to ${endpoint} at: ` + new Date().toString());
+    if (!params) {
+        params = {};
+    }
+    else {
+        params = JSON.parse(params);
+    }
+    try {
+        const response = await axios.post(endpoint, params, options);
+        return await response.data;
+    } catch (error) {
+        throw new Error(error);
+    }
+}
+
+module.exports = {
+    fetch : (endpoint, accessToken)=> fetch(endpoint, accessToken),
+    updateFetch: (endpoint, accessToken, params)=> updateFetch(endpoint, accessToken, params),
+}

+ 31 - 0
src/routes/auth.js

@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+var express = require('express');
+
+const authProvider = require('../auth/AuthProvider');
+const { REDIRECT_URI, POST_LOGOUT_REDIRECT_URI } = require('../authConfig');
+
+const router = express.Router();
+
+router.get('/signin', authProvider.login({
+    scopes: [],
+    redirectUri: REDIRECT_URI,
+    successRedirect: '/'
+}));
+
+router.get('/acquireToken', authProvider.acquireToken({
+    scopes: ['User.Read'],
+    redirectUri: REDIRECT_URI,
+    successRedirect: '/users/profile'
+}));
+
+router.post('/redirect', authProvider.handleRedirect());
+
+router.get('/signout', authProvider.logout({
+    postLogoutRedirectUri: POST_LOGOUT_REDIRECT_URI
+}));
+
+module.exports = router;

+ 17 - 0
src/routes/index.js

@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+var express = require('express');
+var router = express.Router();
+
+router.get('/', function (req, res, next) {
+    res.render('index', {
+        title: 'MSAL Node & Express Web App',
+        isAuthenticated: req.session.isAuthenticated,
+        username: req.session.account?.username,
+    });
+});
+
+module.exports = router;

+ 41 - 0
src/routes/users.js

@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License.
+ */
+
+var express = require('express');
+var router = express.Router();
+
+var fetch = require('../fetch');
+
+var { GRAPH_ME_ENDPOINT } = require('../authConfig');
+
+// custom middleware to check auth state
+function isAuthenticated(req, res, next) {
+    if (!req.session.isAuthenticated) {
+        return res.redirect('/tab-auth'); // redirect to sign-in route
+    }
+
+    next();
+};
+
+router.get('/id',
+    isAuthenticated, // check if user is authenticated
+    async function (req, res, next) {
+        res.render('id', { idTokenClaims: req.session.account.idTokenClaims });
+    }
+);
+
+router.get('/profile',
+    isAuthenticated, // check if user is authenticated
+    async function (req, res, next) {
+        try {
+            const graphResponse = await fetch(GRAPH_ME_ENDPOINT, req.session.accessToken);
+            res.render('profile', { profile: graphResponse });
+        } catch (error) {
+            next(error);
+        }
+    }
+);
+
+module.exports = router;

+ 11 - 0
src/secrets.js

@@ -0,0 +1,11 @@
+const tenantId = "2e58414a-c6ae-43ff-aaf5-45ab8b78a404";
+const clientId = "0673c862-7edc-455d-9636-5559a0a8f2b9";
+const clientSecret = "tOM8Q~x3J8ee-whLd9Cg8sfWAjqRXMCfggIyQcKo";
+const scopes = '.default';
+
+module.exports = {
+    tenantId: tenantId, 
+    clientId: clientId,
+    clientSecret: clientSecret,
+    scopes: scopes
+}

BIN
src/static/favicon.ico


BIN
src/static/images/chevron-up.png


BIN
src/static/images/chevrondown.png


+ 20 - 0
src/static/images/empty_folder.svg

@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg" id="Layer_1" viewBox="0 0 208 208">
+  <style>
+    .st0{opacity:0.12;fill:url(#SVGID_1_);enable-background:new ;} .st1{fill:#C1C1C1;} .st2{fill:#EAEAE9;}
+  </style>
+  <linearGradient id="SVGID_1_" x1="30.809" x2="101.511" y1="104.064" y2="81.088" gradientUnits="userSpaceOnUse" gradientTransform="rotate(1.14 -12088.407 -1327.615)">
+    <stop offset=".16" stop-color="#FFF"/>
+    <stop offset=".23" stop-color="#F6F6F6"/>
+    <stop offset=".35" stop-color="#DDD"/>
+    <stop offset=".5" stop-color="#B4B4B4"/>
+    <stop offset=".67" stop-color="#7B7B7B"/>
+    <stop offset=".87" stop-color="#333"/>
+    <stop offset="1"/>
+  </linearGradient>
+  <path d="M60.8 137l-61 12.2 54.4 24.5 17.3-4.5z" class="st0"/>
+  <title>
+    Files web
+  </title>
+  <path d="M102.7 69.1l-3.5 8.1-57.6 11.6 12.6 84.9 103.4-23.2-9.1-89.8z" class="st1"/>
+  <path d="M54.2 173.7l17-74.8 49.9-11.4 5.2 7.5 40.1-6.5-8.8 62z" class="st2"/>
+</svg>

BIN
src/static/images/file.png


BIN
src/static/images/folder.png


BIN
src/static/images/menu-icon.png


BIN
src/static/images/onedrive.png


+ 1 - 0
src/static/images/onetoc.svg

@@ -0,0 +1 @@
+<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path opacity=".67" fill-rule="evenodd" clip-rule="evenodd" d="M5.5 16h8c.827 0 1.5-.673 1.5-1.5v-13c0-.827-.673-1.5-1.5-1.5h-8C4.673 0 4 .673 4 1.5v13c0 .827.673 1.5 1.5 1.5zM5 1.5a.5.5 0 0 1 .5-.5h8a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-.5.5h-8a.5.5 0 0 1-.5-.5v-13z" fill="#605E5C"/><path d="M15.438 5H15V1h.438c.315 0 .562.26.562.594v2.812c0 .333-.247.594-.563.594h.001z" fill="#AE4BD5"/><path d="M15.438 10H15V6h.438c.315 0 .562.26.562.594v2.812c0 .333-.247.594-.563.594h.001z" fill="#9332BF"/><path d="M15.438 15H15v-4h.438c.315 0 .562.26.562.594v2.812c0 .333-.247.594-.563.594h.001z" fill="#7719AA"/><path d="M5.5 15h8a.5.5 0 0 0 .5-.5v-13a.5.5 0 0 0-.5-.5h-8a.5.5 0 0 0-.5.5v13a.5.5 0 0 0 .5.5z" fill="#fff"/><path d="M1 13h7a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H1a1 1 0 0 0-1 1v7a1 1 0 0 0 1 1z" fill="#7719AA"/><path d="M2.417 10.896V6.443h.085l3.981 3.989.059-.001V6.057" stroke="#fff"/></svg>

+ 1 - 0
src/static/images/pdf.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 7h-2.5c-.827 0-1.5-.673-1.5-1.5V2H4v16h12V7z" fill="#fff"/><path d="M16 6v-.293l-3-3V5.5c0 .275.225.5.5.5H16z" fill="#fff"/><path opacity=".67" fill-rule="evenodd" clip-rule="evenodd" d="M16.707 5 13 1.293A1 1 0 0 0 12.293 1H4a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5.707A1 1 0 0 0 16.707 5zM16 5.707V6h-2.5a.501.501 0 0 1-.5-.5V2.707l3 3zM4 2.2v15.6c0 .11.09.2.2.2h11.6a.2.2 0 0 0 .2-.2V7h-2.5A1.5 1.5 0 0 1 12 5.5V2H4.2a.2.2 0 0 0-.2.2z" fill="#605E5C"/><path d="M14.5 10h-9a.5.5 0 0 1 0-1h9a.5.5 0 0 1 0 1z" fill="#C8C6C4"/><path fill-rule="evenodd" clip-rule="evenodd" d="M6 16v-4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v4a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1zm1.2 0h5.594a.195.195 0 0 0 .2-.196c-.006-.678.002-2.932.006-3.61 0-.11-.09-.194-.2-.194H7.2a.2.2 0 0 0-.2.2v3.6c0 .11.09.2.2.2zm-2.45 0H3a1 1 0 0 1-1-1v-2a1 1 0 0 1 1-1h1.75a.25.25 0 0 1 .25.25v3.5a.25.25 0 0 1-.25.25zm10.5 0H17a1 1 0 0 0 1-1v-2a1 1 0 0 0-1-1h-1.75a.25.25 0 0 0-.25.25v3.5c0 .138.112.25.25.25z" fill="#D65532"/></svg>

+ 1 - 0
src/static/images/photo.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3.5 17h13c.275 0 .5-.225.5-.5v-13c0-.275-.225-.5-.5-.5h-13c-.275 0-.5.225-.5.5v13c0 .275.225.5.5.5z" fill="#fff"/><path clip-rule="evenodd" d="m11.691 10.657-.878 1.286-1.687-2.326a1 1 0 0 0-1.619 0l-2.39 3.296a1 1 0 0 0 .81 1.587h8.146a1 1 0 0 0 .826-1.564l-1.556-2.28a1 1 0 0 0-1.652 0v.001z" stroke="#A6CCC3"/><path clip-rule="evenodd" d="M13.5 7.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" stroke="#FF9810"/><path opacity=".67" fill-rule="evenodd" clip-rule="evenodd" d="M3.5 18h13c.827 0 1.5-.673 1.5-1.5v-13c0-.827-.673-1.5-1.5-1.5h-13C2.673 2 2 2.673 2 3.5v13c0 .827.673 1.5 1.5 1.5zM3 3.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 .5.5v13a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-13z" fill="#605E5C"/></svg>

BIN
src/static/images/plus.png


+ 1 - 0
src/static/images/pptx.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 7h-2.5c-.827 0-1.5-.673-1.5-1.5V2H6v16h12V7z" fill="#fff"/><path d="M18 6v-.293l-3-3V5.5c0 .275.225.5.5.5H18z" fill="#fff"/><path opacity=".67" fill-rule="evenodd" clip-rule="evenodd" d="M18.707 5 15 1.293A1 1 0 0 0 14.293 1H6a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5.707A1 1 0 0 0 18.707 5zM18 5.707V6h-2.5a.501.501 0 0 1-.5-.5V2.707l3 3zM6 2.2v15.6c0 .11.09.2.2.2h11.6a.2.2 0 0 0 .2-.2V7h-2.5A1.5 1.5 0 0 1 14 5.5V2H6.2a.2.2 0 0 0-.2.2z" fill="#605E5C"/><path d="M15.95 11H14l-1-1-1 1v2.489a2.482 2.482 0 0 0 3.437-.408A2.5 2.5 0 0 0 15.95 11z" fill="#ED6C47"/><path d="M13.5 9a2.48 2.48 0 0 0-1.5.509V11h2V9.05a2.51 2.51 0 0 0-.5-.05z" fill="#FF8F6B"/><path d="M15 10h2a2.567 2.567 0 0 0-2-2v2z" fill="#FFC7B5"/><path d="M2 16h8a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1z" fill="#C43E1C"/><path fill-rule="evenodd" clip-rule="evenodd" d="M7.505 8.481C7.175 8.16 6.7 8 6.08 8H4.012v6H5v-2.121h1.015a2.1 2.1 0 0 0 1.044-.253c.3-.169.532-.405.696-.71.164-.304.245-.652.245-1.042 0-.607-.165-1.072-.495-1.393zM5.899 11h-.9V8.988h.932C6.359 8.988 7 8.992 7 10c0 .898-.594 1-1.101 1z" fill="#F9F7F7"/></svg>

BIN
src/static/images/refresh.png


+ 1 - 0
src/static/images/txt.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M16 7h-2.5c-.827 0-1.5-.673-1.5-1.5V2H4v16h12V7z" fill="#fff"/><path d="M16 6v-.293l-3-3V5.5c0 .275.225.5.5.5H16z" fill="#fff"/><path opacity=".67" fill-rule="evenodd" clip-rule="evenodd" d="M16.707 5 13 1.293A1 1 0 0 0 12.293 1H4a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5.707A1 1 0 0 0 16.707 5zM16 5.707V6h-2.5a.501.501 0 0 1-.5-.5V2.707l3 3zM4 2.2v15.6c0 .11.09.2.2.2h11.6a.2.2 0 0 0 .2-.2V7h-2.5A1.5 1.5 0 0 1 12 5.5V2H4.2a.2.2 0 0 0-.2.2z" fill="#605E5C"/><path fill-rule="evenodd" clip-rule="evenodd" d="M5.444 9h9.112c.245 0 .444-.224.444-.5s-.199-.5-.445-.5H5.444C5.199 8 5 8.224 5 8.5s.2.5.444.5zm9.112 2H5.444C5.2 11 5 10.776 5 10.5s.199-.5.444-.5h9.111c.246 0 .445.224.445.5s-.199.5-.444.5zm-9.112 2h9.112c.245 0 .444-.224.444-.5s-.199-.5-.445-.5H5.444c-.245 0-.444.224-.444.5s.2.5.444.5zm0 2h9.112c.245 0 .444-.224.444-.5s-.199-.5-.445-.5H5.444c-.245 0-.444.224-.444.5s.2.5.444.5z" fill="#BDBBB8"/></svg>

BIN
src/static/images/upload.png


+ 1 - 0
src/static/images/xlsx.svg

@@ -0,0 +1 @@
+<svg width="20" height="20" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 7h-2.5c-.827 0-1.5-.673-1.5-1.5V2H6v16h12V7z" fill="#fff"/><path d="M18 6v-.293l-3-3V5.5c0 .275.225.5.5.5H18z" fill="#fff"/><path opacity=".67" fill-rule="evenodd" clip-rule="evenodd" d="M18.707 5 15 1.293A1 1 0 0 0 14.293 1H6a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V5.707A1 1 0 0 0 18.707 5zM18 5.707V6h-2.5a.501.501 0 0 1-.5-.5V2.707l3 3zM6 2.2v15.6c0 .11.09.2.2.2h11.6a.2.2 0 0 0 .2-.2V7h-2.5A1.5 1.5 0 0 1 14 5.5V2H6.2a.2.2 0 0 0-.2.2z" fill="#605E5C"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 12h1c.275 0 .5.225.5.5s-.225.5-.5.5h-1a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5zm3 0h1c.275 0 .5.225.5.5s-.225.5-.5.5h-1a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5z" fill="#134A2C"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12.5 10h1c.275 0 .5.225.5.5s-.225.5-.5.5h-1a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5zm3 0h1c.275 0 .5.225.5.5s-.225.5-.5.5h-1a.501.501 0 0 1-.5-.5c0-.275.225-.5.5-.5z" fill="#21A366"/><path d="M16.5 8h-1c-.275 0-.5.225-.5.5s.225.5.5.5h1c.275 0 .5-.225.5-.5s-.225-.5-.5-.5z" fill="#33C481"/><path d="M13.5 8h-1c-.275 0-.5.225-.5.5s.225.5.5.5h1c.275 0 .5-.225.5-.5s-.225-.5-.5-.5z" fill="#21A366"/><path d="M2 16h8a1 1 0 0 0 1-1V7a1 1 0 0 0-1-1H2a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1z" fill="#107C41"/><path d="m4.606 14 3.707-6H7.15l-3.755 6h1.21z" fill="#fff"/><path d="M7.374 14 3.668 8H4.83l3.754 6h-1.21z" fill="#fff"/></svg>

+ 18 - 0
src/static/scripts/teamsapp.js

@@ -0,0 +1,18 @@
+(function () {
+  "use strict";
+
+  // Call the initialize API first
+  microsoftTeams.app.initialize().then(function () {
+    microsoftTeams.app.getContext().then(function (context) {
+      if (context?.app?.host?.name) {
+        updateHubState(context.app.host.name);
+      }
+    });
+  });
+
+  function updateHubState(hubName) {
+    if (hubName) {
+      // document.getElementById("hubState").innerHTML = "in " + hubName;
+    }
+  }
+})();

+ 306 - 0
src/static/styles/custom.css

@@ -0,0 +1,306 @@
+* {
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0 !important;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu",
+    "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+::-webkit-scrollbar {
+  display: none;
+}
+
+span p {
+  display: inline;
+}
+
+
+html, body {
+  width: 100%;
+  height: 100%;
+  min-width: 1700px;
+}
+.title{
+  width: 100%;
+  height: 50px;
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+  box-sizing: border-box;
+  background-color: white;
+  border-bottom: 2px solid #9b9898;
+  font-weight: bold;
+}
+.content {
+  width: 100%;
+  height: 100%;
+  /* height: auto; */
+  overflow: auto;
+  box-sizing: border-box;
+  background-color: #f5f5f5;
+  display: flex;
+  justify-content: center;
+}
+.wrap {
+  width: 100%;
+  height: 100%;
+  overflow: auto;
+}
+.content nav {
+  width: 15%;
+  background-color: #eceaea;
+  overflow: auto;
+}
+.content section {
+  width: 85%;
+  height: 100%;
+}
+.tree{
+  color:#393939;
+}
+.tree {
+  list-style: none;
+  padding: 10px;
+}
+.tree ul{
+  list-style: none;
+  padding: 10px 0 10px 17px;
+}
+.tree *:before{
+  width:17px;
+  height:17px;
+  display:inline-block;
+}
+.tree label{
+  cursor: pointer;
+}
+.tree label:before{
+  content:'▼';
+  margin-right: 5px;
+}
+
+.tree input[type="checkbox"] {
+  display: none;
+}
+.tree input[type="checkbox"]:checked~ul {
+  display: none;
+}
+.tree input[type="checkbox"]:checked+label:before{
+  content:'▶';
+  margin-right: 5px;
+}
+ul, li {
+  user-select: none;
+  box-sizing: border-box;
+}
+li {
+  padding: 5px 0;
+  font-size: 14px;
+}
+.total {
+  padding: 5px 30px;
+  background-color: #cfcece;
+}
+.menu {
+  display: flex;
+  border-bottom: 1px solid #cfcece;
+}
+.menu > div {
+  padding: 10px 20px;
+  cursor: pointer;
+  display: flex;
+  align-items: center;
+}
+.menu > div:nth-child(2)::before {
+  background-image: url('/static/images/plus.png');
+}
+
+.menu > div:nth-child(3)::before {
+  background-image: url('/static/images/upload.png');
+}
+
+.menu > div:nth-child(4)::before {
+  background-image: url('/static/images/refresh.png');
+}
+
+.menu > div:not(:first-child)::before {
+  content: "";
+  display: inline-block;
+  background-size: 15px 15px;
+  width: 15px;
+  height: 15px;
+  margin-right: 5px;
+}
+
+.menu > div:nth-child(5)::before {
+  background-image: url('/static/images/onedrive.png');
+  background-size: 20px 20px;
+  width: 20px;
+  height: 20px;
+}
+.panel {
+  padding: 10px 20px;
+}
+.file-title {
+  display: flex;
+  padding: 10px 20px;
+  border-bottom: 1px solid #cfcece;
+}
+
+.file-title > div:hover{
+  cursor: pointer;
+}
+.file-title > div{
+  user-select: none;
+}
+.file-title > div.on:not(:first-child):after {
+  transform: rotate(180deg);
+}
+.file-title > div:not(:first-child):after {
+  content: '';
+  background-image: url('/static/images/chevrondown.png');
+  width: 15px;
+  height: 15px;
+  margin-left: 5px;
+  display: inline-block;
+  background-size: 15px 15px;
+}
+.file-content {
+  width: 100%;
+  height: calc(100% - 136px);
+}
+.file-content > div {
+  display: flex;
+  padding: 10px 20px;
+}
+.file-content > div > div:not(:first-child):hover{
+  cursor: pointer;
+}
+.file-content > div > div:nth-child(1),
+.file-title > div:nth-child(1){
+  width: 5%;
+  text-align: right;
+}
+.file-content > div > div:nth-child(2),
+.file-title > div:nth-child(2) {
+  width: 60%;
+  padding-left: 20px;
+}
+.file-content > div > div:nth-child(3),
+.file-title > div:nth-child(3) {
+  width: 5%;
+}
+.file-content > div > div:nth-child(6),
+.file-title > div:nth-child(6),
+.file-content > div > div:nth-child(5),
+.file-title > div:nth-child(5),
+.file-content > div > div:nth-child(4),
+.file-title > div:nth-child(4) {
+  width: 10%;
+}
+
+.circle-name{
+  border-radius: 50%;
+  width: 35px;
+  height: 35px;
+  background-color: #b92e2eec;
+  display: flex;
+  align-items: center;
+  margin-right: 10px;
+  color: white;
+  overflow: hidden;
+  justify-content: center;
+  min-width: 35px;
+}
+
+.empty-box {
+  background-image: url('/static/images/empty_folder.svg');
+  width: 100%;
+  height: 100%;
+  background-repeat: no-repeat;
+  background-position: center;
+}
+
+.empty-box .text {
+  padding: 16px 16px 0px;
+  font-family: "Segoe UI", "Segoe UI Web (West European)", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
+  -webkit-font-smoothing: antialiased;
+  font-size: 20px;
+  font-weight: 600;
+  max-width: 400px;
+  color: rgb(96, 94, 92);
+  margin: 0px auto;
+}
+
+.file-content > div:hover{
+  background-color: #eeeeee;
+  cursor: pointer;
+}
+.file-content input[type='checkbox'] {
+  cursor: pointer;
+}
+.file-content > div > div:nth-child(1) {
+  display: flex;
+  justify-content: space-between;
+}
+.file-content > div.on {
+  background-color: #eeeeee;
+}
+.file-content > div {
+  user-select: none;
+}
+.toggle-box {
+  position: absolute;
+  width: 220px;
+  background-color: #eeeeee;
+  border: 1px solid #929191;
+  box-shadow: 1px 2px 2px 2px #929191;
+}
+.toggle-box > div {
+  padding: 10px;
+  cursor: pointer;
+  user-select: none;
+}
+.toggle-box > div:hover {
+  background-color: #c5c4c4;
+}
+.modal {
+  display: flex;
+  position: fixed;
+  width: 100%;
+  height: 100%;
+  top: 0;
+  left: 0;
+  background-color: rgba(0, 0, 0, 0.4);
+  align-items: center;
+  justify-content: center;
+  display: none;
+}
+
+.modal-content {
+  padding: 20px;
+  width: 400px;
+  height: 225px;
+  z-index: 2;
+  background-color: white;
+  box-shadow: 2px 2px 2px 2px rgb(75, 75, 75);
+}
+.modal .modal-content .title {
+  font-weight: bold;
+  font-size: 18px;
+  color: #5f5d5d;
+}
+
+.sp-name:hover {
+  text-decoration: underline;
+}
+
+.tree li > ul {
+  padding: 0px 0px 0px 35px;
+  margin-block-end : 0.5rem;
+  margin-block-start: 0;
+}

+ 647 - 0
src/views/hello.html

@@ -0,0 +1,647 @@
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <link rel="icon" href="/favicon.ico" />
+    <link rel="stylesheet" type="text/css" href="/static/styles/custom.css?v=05" />
+    <script
+      src="https://res.cdn.office.net/teams-js/2.17.0/js/MicrosoftTeams.min.js"
+      integrity="sha384-xp55t/129OsN192JZYLP0rGhzjCF9aYtjY0LVtXvolkDrBe4Jchylp56NrUYJ4S2"
+      crossorigin="anonymous"
+    ></script>
+    <script src="https://secure.aadcdn.microsoftonline-p.com/lib/1.2.1/js/msal.js"></script>
+    <script src="/static/scripts/teamsapp.js"></script>
+    <script src="https://code.jquery.com/jquery-latest.min.js"></script>
+    <title>Microsoft Teams Tab</title>
+    <style>
+    </style>
+  </head>
+
+  <body>
+      <div class="wrap">
+        <div class="content">
+            <nav>
+                <div class="tree">
+                    <li class="">
+                        <input type="checkbox" id="file">
+                        <label for="file">내 OFFICE 365 파일</label>
+                    </li>
+                </div>
+                <div class="total">모두</div>
+                <div class="tree group"></div>
+            </nav>
+            <section>
+                <div class="menu">
+                    <div class="sub-menu">
+                        <img src="/static/images/menu-icon.png" width="30" height="30" alt="메뉴 아이콘">
+                    </div>
+                    <div>새로만들기</div>
+                    <div>업로드</div>
+                    <div>새로고침</div>
+                    <div class="one-drive-button">OneDrive에서 열기</div>
+                </div>
+                <div class="panel"></div>
+                <div class="file-title">
+                    <div><img src="/static/images/file.png" width="20" alt="파일"></div>
+                    <div class="head-name" onclick="sorting('name')">이름</div>
+                    <div class="head-size" onclick="sorting('size')">용량</div>
+                    <div class="head-lastModifiedDateTime" onclick="sorting('lastModifiedDateTime')">수정된 날짜</div>
+                    <div class="head-lastModifiedBy.user.displayName" onclick="sorting('lastModifiedBy.user.displayName')">수정한 사람</div>
+                    <div class="head-label" onclick="sorting('label')">민감도</div>
+                </div>
+                <div class="file-content"></div>
+            </section>
+        </div>
+    </div>
+    <div class="modal">
+        <div class="modal-content">
+            <div class="title">이름 바꾸기</div>
+            <input type="text" name="file_name" id="" value="">.pdf
+            <div>
+                <div class="name-btn">이름 바꾸기</div>
+                <div class="name-btn">취소</div>
+            </div>
+        </div>
+    </div>
+  </body>
+</html>
+<script>
+  const groupId = '3df73dac-a8bc-4dd0-9159-fdb2c696c067';
+  const groupMap = new Map();
+  let _selectedData = [];
+  $(()=>{
+    microsoftTeams.appInitialization.notifySuccess();
+    getGroupList();
+    // callApi('get', '/me/drive/root/children', (jsonData)=>{
+    //   console.log('=======================one drive==========================');
+    //   console.log(jsonData);
+    //   console.log('============================================================');
+    //   let str = `<li>
+    //                 <input type="checkbox" checked="false" id="my-one-drive" onclick="showOneDrive()">
+    //                 <label for="my-one-drive">One Drive</label>
+    //                 <ul>`;
+    //                   if (jsonData && jsonData.value) {
+    //                     const drives = jsonData.value;
+    //                     drives.forEach((drive)=>{
+    //                       str +=`<li id="${drive.id}" onclick="DrawOneDrive(${drive.id})">${drive.name}</li>`
+    //                     })
+    //                   }
+    //         str += `</ul>
+    //             </li>`;
+    //   $('.group').append(str);
+    // });
+    // callApi('get', '/me/joinedTeams', (jsonData)=>{
+    //   console.log('======================joinedTeams======================')
+    //   console.log(jsonData);
+    //   if (jsonData && jsonData.value && jsonData.value.length) {
+    //     // jsonData.value.forEach((team)=>{
+    //     //     team.id
+    //     //     callApi('get', '/')
+    //     // })
+    //   }
+    //   // let str = `<li>
+    //   //               <input type="checkbox" checked="false" id="my-one-drive" onclick="showOneDrive()">
+    //   //               <label for="my-one-drive">One Drive</label>
+    //   //               <ul>`;
+    //   //                 if (jsonData && jsonData.value) {
+    //   //                   const drives = jsonData.value;
+    //   //                   drives.forEach((drive)=>{
+    //   //                     str +=`<li id="${drive.id}" onclick="DrawOneDrive(${drive.id})">${drive.name}</li>`
+    //   //                   })
+    //   //                 }
+    //   //       str += `</ul>
+    //   //           </li>`;
+    // });
+    // callApi('get', '/sites', (jsonData)=>{
+    //   console.log('=====================sites===================');
+    //   console.log(jsonData);
+    // });
+    // callApi('get', '/me/drives', (jsonData)=>{
+    //   console.log('=====================/me/drives===================');
+    //   console.log(jsonData);
+    //   // drawOneDrive(jsonData);
+    // })
+    // getGroupList();
+    // callApi('get', '/groups/'+groupId+'/drive/items/root/children', (jsonData) =>drawFileList(jsonData, groupId));
+    //
+    window.oncontextmenu = function () {
+        return false;
+      };
+    });
+
+    function getFileList(id) {
+      // callApi('get', "/"+id+"/drive/root/children", "Files.Read", drawFileList, id);
+      callApi('get', "/users/"+id+"/drive", drawFileList, id);
+    }
+
+    // function drawList(jsonData) {
+    //   if (jsonData && jsonData.value) {
+    //       const group = $('.group');
+    //       let str = "";
+    //       jsonData.value.forEach(element => {
+    //           str+=`<li onclick="getFileList('${element.userPrincipalName}')">
+    //                   <div style="display:flex;">
+    //                     <div class="circle-name">${element.givenName}</div>
+    //                     <div style="display:flex; flex-direction:column; justify-content:space-between;">
+    //                       <div style="font-size: 16px; font-weight:bold;">${element.displayName}</div>
+    //                       <div style="font-size: 12px; color: #7b7777ec;">${element.mail}</div>
+    //                     </div>
+    //                   </div>
+    //                 </li>`
+    //       });
+
+          
+    //       group.html(str);
+    //   }
+    // }
+
+    function drawFileList(jsonData) {
+              _selectedData = [];    
+      if (jsonData && jsonData.value && jsonData.value.length > 0) {
+              _selectedData = jsonData.value;
+              let str = "";
+              const $fileContent = $('.file-content');
+              const $oneDriveBtn  = $('.one-drive-button');
+              const panel = $('.panel');
+              let panelStr = "";
+
+              jsonData.value.forEach((obj, idx)=>{
+                if (idx === 0 && obj.parentReference) {
+                  const parent = $('#' + obj.parentReference.id);
+                  if (parent) {
+                    console.log(parent);
+                  }
+                  console.log(obj.parentReference.id);
+                  panelStr += `<span class="panel-item on" id="${obj.parentReference.id}" onclick="findChildrenItems(${obj.parentReference.id})">${obj.parentReference.name}</span>`
+                  
+                  panel.html(panelStr);
+                }
+                dragAndDrop(obj.id);
+                let images = "/static/images/";
+                $oneDriveBtn.off('click');
+                if (obj.webUrl){
+                  $oneDriveBtn.on('click', ()=>{
+                    window.open(obj.webUrl);
+                  })
+                }
+
+                let modifyName = "";
+                if (obj.lastModifiedBy && obj.lastModifiedBy.user) {
+                  modifyName = obj.lastModifiedBy.user.displayName;
+                }
+                if (obj.folder || obj.driveType) {
+                  images += "folder.png";
+                }
+                else if (obj.file && obj.name) {
+                  let ext = obj.name.substring(obj.name.lastIndexOf('.') + 1);
+                  if (obj.file.mimeType && obj.file.mimeType.includes('image')){
+                    ext = 'photo';
+                  }
+                  images += ext+ ".svg";
+                }
+                else {
+                  images += "file.png";
+                }
+                let amount = '';
+                if (obj.quota) {
+                  amount = getVolume(obj.quota.remaining);
+                }
+                if (obj.size) {
+                  amount = getVolume(obj.size);
+                }
+                let modifyDate = obj.lastModifiedDateTime;
+                if (modifyDate) {
+                  modifyDate = modifyDate.replace('T', ' ');
+                  modifyDate = modifyDate.substring(5, 7)+ '월 '+ modifyDate.substring(8, 10)+ '일';
+                }
+                str += `
+                  <div id="row_${idx}">
+                    <div>
+                      <input type="checkbox" name="row_${idx}" value="${obj.id}" onclick="event.stopPropagation()">
+                      <img src="${images}" width="20" height="20" alt="폴더 이미지">
+                    </div>
+                    <div class="file_name"><span class="sp-name" onclick="findChildrenItems('${obj.id}', '${obj.name}')">${obj.name}</span></div>
+                    <div>${amount}</div>
+                    <div>${modifyDate}</div>
+                    <div>${modifyName}</div>
+                    <div></div>
+                  </div>`
+              });
+              $fileContent.html(str);
+              $('.file-content > div').on('click', function(e){
+                e.preventDefault();
+                const checkbox = $('input[name="' + $(this).attr('id') + '"]');
+                if (checkbox[0]) {
+                  const isChecked = checkbox.prop("checked");
+                  checkbox.prop('checked', !isChecked);
+                  const method = !isChecked ? 'addClass' : 'removeClass';
+                  $(this)[method]('on');
+                }
+              });
+
+              window.addEventListener('click', ()=>{
+                  if ($('.toggle-box')[0]){
+                    $('.toggle-box').remove();
+                  }
+              })
+              $('.file-content > div').on('mousedown', function(e){
+                if ((e.button == 2) || (e.which == 3)) {
+                  const checkbox = $('input[name="'+$(this).attr('id') + '"]');
+                  if (!$(this).hasClass('on')) {
+                    checkbox.prop('checked', true);
+                    $(this).addClass('on');
+                  }
+
+                  if ($('.toggle-box')[0]) {
+                    $('.toggle-box').remove();
+                  }
+                  const {clientX, clientY} = e;
+                    const toggleBox = $(`<div class="toggle-box" style="position:absolute; top:${clientY}; left: ${clientX}">
+                        <div>편집</div>
+                        <div>탭으로 설정</div>
+                        <div>다운로드</div>
+                        <div>삭제</div>
+                        <div>이동</div>
+                        <div>복사</div>
+                        <div onclick="nameChange()">이름 바꾸기</div>
+                      </div>`);
+                      $('body').append(toggleBox);
+                }
+              });
+            }
+    }
+
+    function drawOneDrive(jsonData, name) {
+      _selectedData = [];
+      const panel = $('.panel');
+      let panelStr = `<span class="panel-item on" onclick="showOneDrive()">One Drive</span>`;
+      panel.html(panelStr);
+      if (jsonData && jsonData.value && jsonData.value.length > 0) {
+        _selectedData = jsonData.value;
+              let str = "";
+              const $fileContent = $('.file-content');
+              const $oneDriveBtn  = $('.one-drive-button');
+              
+              jsonData.value.forEach((obj, idx)=>{
+                console.log(obj);
+                dragAndDrop(obj.id);
+                let images = "/static/images/";
+                $oneDriveBtn.off('click');
+                if (obj.webUrl){
+                  $oneDriveBtn.on('click', ()=>{
+                    window.open(obj.webUrl);
+                  })
+                }
+
+                let modifyName = "";
+                if (obj.lastModifiedBy && obj.lastModifiedBy.user) {
+                  modifyName = obj.lastModifiedBy.user.displayName;
+                }
+                if (obj.folder || obj.driveType) {
+                  images += "folder.png";
+                }
+                else if (obj.file && obj.name) {
+                  let ext = obj.name.substring(obj.name.lastIndexOf('.') + 1);
+                  if (obj.file.mimeType && obj.file.mimeType.includes('image')){
+                    ext = 'photo';
+                  }
+                  images += ext+ ".svg";
+                }
+                else {
+                  images += "file.png";
+                }
+                let amount = '0 Bytes';
+                if (obj.size) {
+                  amount = getVolume(obj.size);
+                }
+                let modifyDate = obj.lastModifiedDateTime;
+                if (modifyDate) {
+                  modifyDate = modifyDate.replace('T', ' ');
+                  modifyDate = modifyDate.substring(5, 7)+ '월 '+ modifyDate.substring(8, 10)+ '일';
+                }
+                str += `
+                  <div id="row_${idx}">
+                    <div>
+                      <input type="checkbox" name="row_${idx}" value="${obj.id}" onclick="event.stopPropagation()">
+                      <img src="${images}" width="20" height="20" alt="폴더 이미지">
+                    </div>
+                    <div class="file_name"><span class="sp-name" onclick="findOneDriveChildrenItems('${obj.id}')">${obj.name}</span></div>
+                    <div>${amount}</div>
+                    <div>${modifyDate}</div>
+                    <div>${modifyName}</div>
+                    <div></div>
+                  </div>`
+              });
+              $fileContent.html(str);
+              $('.file-content > div').on('click', function(e){
+                e.preventDefault();
+                const checkbox = $('input[name="'+$(this).attr('id') + '"]');
+                if (checkbox[0]) {
+                  const isChecked = checkbox.prop("checked");
+                  checkbox.prop('checked', !isChecked);
+                  const method = !isChecked ? 'addClass' : 'removeClass';
+                  $(this)[method]('on');
+                }
+              });
+
+              window.addEventListener('click', ()=>{
+                  if ($('.toggle-box')[0]){
+                    $('.toggle-box').remove();
+                  }
+              })
+              $('.file-content > div').on('mousedown', function(e){
+                if ((e.button == 2) || (e.which == 3)) {
+                  const checkbox = $('input[name="'+$(this).attr('id') + '"]');
+                  if (!$(this).hasClass('on')) {
+                    checkbox.prop('checked', true);
+                    $(this).addClass('on');
+                  }
+
+                  if ($('.toggle-box')[0]) {
+                    $('.toggle-box').remove();
+                  }
+                  const {clientX, clientY} = e;
+                    const toggleBox = $(`<div class="toggle-box" style="position:absolute; top:${clientY}; left: ${clientX}">
+                        <div>편집</div>
+                        <div>탭으로 설정</div>
+                        <div>다운로드</div>
+                        <div>삭제</div>
+                        <div>이동</div>
+                        <div>복사</div>
+                        <div onclick="nameChange()">이름 바꾸기</div>
+                      </div>`);
+                      $('body').append(toggleBox);
+                }
+              });
+        }
+    }
+
+    // callApi('get','/deviceManagement', (jsonData)=>{
+    //     console.log(jsonData);
+    // })
+
+    function nameChange() {
+        if ($('.file-content > .on .file_name')[0]) {
+          console.log($('.file-content > .on .file_name').text());
+        }
+    }
+
+    function dragAndDrop(driveId) {
+      const $fileContent = $('.file-content');
+      $fileContent.off('dragover');
+      $fileContent.off('dragleave');
+      $fileContent.off('drop');
+
+      $fileContent.on('dragover', function(e){
+          e.preventDefault();
+          $(this).css('background-color', '#eee');
+      });
+      $fileContent.on('dragleave', function(e){
+          $(this).css('background-color', '#f5f5f5');
+      })
+
+      $fileContent.on("drop", function(e){
+            e.preventDefault();
+            $(this).css('background-color', '#f5f5f5');
+            const file = e.originalEvent.dataTransfer.files[0];
+            encryptFile(file, driveId);
+            // if (file && file.type.startsWith("image")) {
+            //     displayImage(file);
+            // }
+      });
+    }
+
+    function encryptFile(file, driveId) {
+      const extArr = [ 
+        'lnk', 'exe', 'com', 'cmd', 'bat', 'dll', 'ini', 
+        'pst', 'sca', 'drm', 'sys', 'cpl', 'inf', 'drv', 
+        'dat', 'tmp', 'msp', 'msi', 'pdb', 'jar'
+      ]
+      if (file && file.name) {
+        const ext = file.name.substring(file.name.lastIndexOf('.') + 1);
+        if (extArr.includes(ext)) {
+          const modal = `<div class="modal error" style="display: flex;">
+                            <div style="display:flex; border:1px solid red; flex-direction: column; padding:20px;justify-content:center;width:400px;height:120px; background-color:white;">
+                                <div style='width:100%;margin-bottom:10px; height:40px;display:flex;justify-content:space-between; align-items:center; font-weight: bold; font-size: 18px;'>
+                                  <div style="">파일 형식 오류</div>
+                                  <div onclick="modalClose()" style="cursor:pointer; user-select:none;">x</div>
+                                </div>
+                                <div>
+                                  암호화 할수 없는 형식의 파일입니다.<br>
+                                  파일명 : ${file.name}
+                                </div>
+                            </div>
+                        </div>`;
+          $('body').append($(modal));
+        }
+
+        uploadFiles(itemId)
+      }
+
+    }
+
+    function modalClose() {
+      if ($('.modal.error')[0]){
+        $('.modal.error').remove();
+      }
+    }
+
+
+    function callApi(type, uri, callBackMethod, args, params) {
+        $.ajax({
+            method: 'post',
+            url : "/api-"+type,
+            data: {
+              api_uri: uri,
+              param : JSON.stringify(params),
+            },
+            success: (res)=> {
+              callBackMethod(res, args);
+            },
+            error: (error)=> {
+              console.log(error);
+            }
+        });
+    }
+
+    function makeFolder(type, uri, scopes, callBackMethod, args, params) {
+      $.ajax({
+            method: 'post',
+            url : "/api-"+type,
+            data: {
+              api_uri: uri,
+              scopes : scopes,
+              param : params,
+            },
+            success: (res)=> {
+              callBackMethod(res, args);
+            },
+            error: (error)=> {
+              console.log(error);
+            }
+      });
+    }
+
+    function findChildrenItems(id, type) {
+      window.event.preventDefault();
+      window.event.stopPropagation();
+
+      // const param = {
+      //     "requests": [
+      //         {
+      //             "entityTypes": [
+      //                 "driveItem"
+      //             ],
+      //             "query": {
+      //                 "queryString": id
+      //             }
+      //         }
+      //     ]
+      // }
+      // 
+      // callApi('post', '/search/query', (jsonData)=>{
+      //   console.log(jsonData);
+      // },null, param);
+      if ($('#' + id)[0]) return;
+      let uri = '/groups/'+groupId+'/drive/items/'+id+'/children';
+      callApi('get', uri, (jsonData)=>{
+        drawFileList(jsonData);
+      },null);
+    }
+
+    function findOneDriveChildrenItems(id) {
+      callApi('get', '/me/drive/items/'+id+'/children', (jsonData)=>{
+        drawOneDrive(jsonData);
+      },null);
+    }
+    // $.ajax({
+    //   url: '/test',
+    //   method : 'post',
+    //   success: function(res){
+    //     console.log(res);
+    //   },
+    //   error: function(error) {
+    //     console.log(error);
+    //   }
+    // })
+
+    function getVolume(amount) {
+      const tb = 1099511627776;
+      const gb = 1073741824;
+      const mb = 1048576;
+      const kb = 1024;
+      if (!isNaN(Number(amount)) && amount > 0) {
+        if (amount >= tb) {
+          amount = (amount/tb).toFixed(2) + 'TB';
+        }
+        else if (amount >= gb){
+          amount = (amount/gb).toFixed(2) + 'GB';
+        }
+        else if (amount >= mb){
+          amount = (amount/mb).toFixed(2) + 'MB';
+        }
+        else if (amount >= kb){
+          amount = (amount/kb).toFixed(2) + 'KB';
+        }
+        else {
+          amount += "Byte";
+        }
+      }
+
+      return amount;
+    }
+
+    function uploadFiles(itemId) {
+      //
+
+      $.ajax({
+            method: 'post',
+            url : "/api-post",
+            data: {
+              api_uri: '/groups/'+groupId+'/drive/items/'+itemId+'/content',
+              scopes : 'Files.ReadWrite',
+              param : params,
+            },
+            success: (res)=> {
+              callBackMethod(res, args);
+            },
+            error: (error)=> {
+              console.log(error);
+            }
+      });
+    }
+
+    function getGroupList() {
+      $.ajax({
+          method: 'post',
+          url : "/getGroupList",
+          success: (res)=> {
+            drawList(res);
+          },
+          error: (error)=> {
+            console.log(error);
+          }
+      })
+    }
+
+    function drawList(jsonData) {
+      const group = $('.group');
+      group.empty();
+      if (jsonData) {
+          const {oneDrive, joinedTeams, sites} = jsonData;
+          let str = `<li>
+                      <input type="checkbox" checked="false" id="my-one-drive" onclick="showOneDrive()">
+                      <label for="my-one-drive">One Drive</label>
+                      <ul>`;
+                      if (oneDrive && oneDrive.length > 0) {
+                        oneDrive.forEach((drive)=>{
+                          str +=`<li id="${drive.id}" onclick="DrawOneDriveFile('${drive.id}', '${drive.name}')">${drive.name}</li>`
+                        })
+                      }
+              str += `</ul>
+                    </li>`;
+              if (joinedTeams) {
+                joinedTeams.teams.forEach((team)=>{
+                  str += `<li>
+                            <input type="checkbox" checked="false" id="joined-team" onclick="showJoinedTeam('${team.id}')">
+                            <label for="joined-team">${team.displayName}</label>
+                            <ul>`;
+                            console.log('팀 : ',team);
+                            const items = joinedTeams.items[team.id];
+                            if (items && items.length > 0) {
+                              items.forEach((item)=>{
+                                str +=`<li id="${item.id}" onclick="showJoinedTeam('${item.id}', '${item.name}')">${item.name}</li>`
+                              })
+                            }
+                  str +=`</ul></li>`;
+                })
+              }
+              group.append($(str));
+      }
+    }
+
+    function showOneDrive() {
+      callApi('get', '/me/drive/root/children', (jsonData)=>{
+        drawOneDrive(jsonData, 'One Drive', null);
+      })
+    }
+
+    function DrawOneDriveFile(id, name) {
+      callApi('get', '/me/drive/items/' + id, (jsonData)=>{
+        drawOneDrive(jsonData, name, id);
+      })
+    }
+
+    function showJoinedTeam(id, name) {
+      callApi('get', '/groups/'+id+'/drive/items/root/children', (jsonData)=>{
+        drawFileList(jsonData, name, id);
+      })
+    }
+    function sorting(type) {
+      console.log($('.head-'+type));
+      console.log(_selectedData);
+      // _selectedData.sort(function (a, b){
+
+      // })
+    }
+</script>

+ 81 - 0
teamsapp.local.yml

@@ -0,0 +1,81 @@
+# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.0.0/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: 1.0.0
+
+provision:
+  # Creates a Teams app
+  - uses: teamsApp/create
+    with:
+      # Teams app name
+      name: share${{APP_NAME_SUFFIX}}
+    # Write the information of created resources into environment file for
+    # the specified environment variable(s).
+    writeToEnvironmentFile:
+      teamsAppId: TEAMS_APP_ID
+
+  # Set TAB_DOMAIN and TAB_ENDPOINT for local launch
+  - uses: script
+    with:
+      run:
+        echo "::set-teamsfx-env TAB_DOMAIN=localhost:53000";
+        echo "::set-teamsfx-env TAB_ENDPOINT=https://localhost:53000";
+  # Validate using manifest schema
+  - uses: teamsApp/validateManifest
+    with:
+      # Path to manifest template
+      manifestPath: ./appPackage/manifest.json
+  # Build Teams app package with latest env value
+  - uses: teamsApp/zipAppPackage
+    with:
+      # Path to manifest template
+      manifestPath: ./appPackage/manifest.json
+      outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+      outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
+  # Validate app package using validation rules
+  - uses: teamsApp/validateAppPackage
+    with:
+      # Relative path to this file. This is the path for built zip file.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+  # Apply the Teams app manifest to an existing Teams app in
+  # Teams Developer Portal.
+  # Will use the app id in manifest file to determine which Teams app to update.
+  - uses: teamsApp/update
+    with:
+      # Relative path to this file. This is the path for built zip file.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+  # Extend your Teams app to Outlook and the Microsoft 365 app
+  - uses: teamsApp/extendToM365
+    with:
+      # Relative path to the build app package.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+    # Write the information of created resources into environment file for
+    # the specified environment variable(s).
+    writeToEnvironmentFile:
+      titleId: M365_TITLE_ID
+      appId: M365_APP_ID
+
+deploy:
+  # Install development tool(s)
+  - uses: devTool/install
+    with:
+      devCert:
+        trust: true
+    # Write the information of installed development tool(s) into environment
+    # file for the specified environment variable(s).
+    writeToEnvironmentFile:
+      sslCertFile: SSL_CRT_FILE
+      sslKeyFile: SSL_KEY_FILE
+  # Run npm command
+  - uses: cli/runNpmCommand
+    name: install dependencies
+    with:
+      args: install --no-audit
+  # Generate runtime environment variables
+  - uses: file/createOrUpdateEnvironmentFile
+    with:
+      target: ./.localConfigs
+      envs:
+        PORT: 53000
+        SSL_CRT_FILE: ${{SSL_CRT_FILE}}
+        SSL_KEY_FILE: ${{SSL_KEY_FILE}}

+ 137 - 0
teamsapp.yml

@@ -0,0 +1,137 @@
+# yaml-language-server: $schema=https://aka.ms/teams-toolkit/1.0.0/yaml.schema.json
+# Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
+# Visit https://aka.ms/teamsfx-actions for details on actions
+version: 1.0.0
+
+environmentFolderPath: ./env
+
+# Triggered when 'teamsapp provision' is executed
+provision:
+  # Creates a Teams app
+  - uses: teamsApp/create
+    with:
+      # Teams app name
+      name: share${{APP_NAME_SUFFIX}}
+    # Write the information of created resources into environment file for
+    # the specified environment variable(s).
+    writeToEnvironmentFile:
+      teamsAppId: TEAMS_APP_ID
+
+  - uses: arm/deploy # Deploy given ARM templates parallelly.
+    with:
+      # AZURE_SUBSCRIPTION_ID is a built-in environment variable,
+      # if its value is empty, TeamsFx will prompt you to select a subscription.
+      # Referencing other environment variables with empty values
+      # will skip the subscription selection prompt.
+      subscriptionId: ${{AZURE_SUBSCRIPTION_ID}}
+      # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable,
+      # if its value is empty, TeamsFx will prompt you to select or create one
+      # resource group.
+      # Referencing other environment variables with empty values
+      # will skip the resource group selection prompt.
+      resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}}
+      templates:
+        - path: ./infra/azure.bicep # Relative path to this file
+          # Relative path to this yaml file.
+          # Placeholders will be replaced with corresponding environment
+          # variable before ARM deployment.
+          parameters: ./infra/azure.parameters.json
+          # Required when deploying ARM template
+          deploymentName: Create-resources-for-tab
+      # Teams Toolkit will download this bicep CLI version from github for you,
+      # will use bicep CLI in PATH if you remove this config.
+      bicepCliVersion: v0.9.1
+
+  # Validate using manifest schema
+  - uses: teamsApp/validateManifest
+    with:
+      # Path to manifest template
+      manifestPath: ./appPackage/manifest.json
+  # Build Teams app package with latest env value
+  - uses: teamsApp/zipAppPackage
+    with:
+      # Path to manifest template
+      manifestPath: ./appPackage/manifest.json
+      outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+      outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
+  # Validate app package using validation rules
+  - uses: teamsApp/validateAppPackage
+    with:
+      # Relative path to this file. This is the path for built zip file.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+  # Apply the Teams app manifest to an existing Teams app in
+  # Teams Developer Portal.
+  # Will use the app id in manifest file to determine which Teams app to update.
+  - uses: teamsApp/update
+    with:
+      # Relative path to this file. This is the path for built zip file.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+  # Extend your Teams app to Outlook and the Microsoft 365 app
+  - uses: teamsApp/extendToM365
+    with:
+      # Relative path to the build app package.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+    # Write the information of created resources into environment file for
+    # the specified environment variable(s).
+    writeToEnvironmentFile:
+      titleId: M365_TITLE_ID
+      appId: M365_APP_ID
+
+# Triggered when 'teamsapp deploy' is executed
+deploy:
+  # Run npm command
+  - uses: cli/runNpmCommand
+    name: install dependencies
+    with:
+      args: install --production
+  # Deploy your application to Azure App Service using the zip deploy feature.
+  # For additional details, refer to https://aka.ms/zip-deploy-to-app-services.
+  - uses: azureAppService/zipDeploy
+    with:
+      # Deploy base folder
+      artifactFolder: .
+      # Ignore file location, leave blank will ignore nothing
+      ignoreFile: .webappignore
+      # The resource id of the cloud resource to be deployed to.
+      # This key will be generated by arm/deploy action automatically.
+      # You can replace it with your existing Azure Resource id
+      # or add it to your environment variable file.
+      resourceId: ${{TAB_AZURE_APP_SERVICE_RESOURCE_ID}}
+
+# Triggered when 'teamsapp publish' is executed
+publish:
+  # Validate using manifest schema
+  - uses: teamsApp/validateManifest
+    with:
+      # Path to manifest template
+      manifestPath: ./appPackage/manifest.json
+  # Build Teams app package with latest env value
+  - uses: teamsApp/zipAppPackage
+    with:
+      # Path to manifest template
+      manifestPath: ./appPackage/manifest.json
+      outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+      outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
+  # Validate app package using validation rules
+  - uses: teamsApp/validateAppPackage
+    with:
+      # Relative path to this file. This is the path for built zip file.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+  # Apply the Teams app manifest to an existing Teams app in
+  # Teams Developer Portal.
+  # Will use the app id in manifest file to determine which Teams app to update.
+  - uses: teamsApp/update
+    with:
+      # Relative path to this file. This is the path for built zip file.
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+  # Publish the app to
+  # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps)
+  # for review and approval
+  - uses: teamsApp/publishAppPackage
+    with:
+      appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
+    # Write the information of created resources into environment file for
+    # the specified environment variable(s).
+    writeToEnvironmentFile:
+      publishedAppId: TEAMS_APP_PUBLISHED_APP_ID
+projectId: 0489f4a2-d1d9-447d-b0ad-2b1f854410b6

+ 60 - 0
web.config

@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     This configuration file is required if iisnode is used to run node processes behind
+     IIS or IIS Express.  For more information, visit:
+
+     https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
+-->
+
+<configuration>
+  <system.webServer>
+    <webSocket enabled="false" />
+    <handlers>
+      <!-- Indicates that the app.js file is a node.js site to be handled by the iisnode module -->
+      <add name="iisnode" path="src/app.js" verb="*" modules="iisnode"/>
+    </handlers>
+    <rewrite>
+      <rules>
+        <!-- Do not interfere with requests for node-inspector debugging -->
+        <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
+          <match url="^src/app.js\/debug[\/]?" />
+        </rule>
+
+        <!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
+        <rule name="StaticContent">
+          <action type="Rewrite" url="public{PATH_INFO}"/>
+        </rule>
+
+        <!-- All other URLs are mapped to the node.js site entry point -->
+        <rule name="DynamicContent">
+          <conditions>
+            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
+          </conditions>
+          <action type="Rewrite" url="src/app.js"/>
+        </rule>
+      </rules>
+    </rewrite>
+
+    <!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
+    <security>
+      <requestFiltering>
+        <hiddenSegments>
+          <remove segment="bin"/>
+        </hiddenSegments>
+      </requestFiltering>
+    </security>
+
+    <!-- Make sure error responses are left untouched -->
+    <httpErrors existingResponse="PassThrough" />
+
+    <!--
+      You can control how Node is hosted within IIS using the following options:
+        * watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
+        * node_env: will be propagated to node as NODE_ENV environment variable
+        * debuggingEnabled - controls whether the built-in debugger is enabled
+
+      See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options
+    -->
+    <!--<iisnode watchedFiles="web.config;*.js"/>-->
+  </system.webServer>
+</configuration>