From b0ffb317f5527730968990abccef2126411bed2f Mon Sep 17 00:00:00 2001 From: Daniel Salazar Date: Fri, 21 Nov 2025 12:00:07 -0800 Subject: [PATCH] fix: eslint autofixable errors --- doc/contributors/extensions/gen.js | 12 +- .../extensions/manual_overrides.json.js | 12 +- doc/self-hosters/config-vals.json.js | 24 +- doc/self-hosters/gen.js | 8 +- eslint.config.js | 84 +- eslint/bang-space-if.js | 2 +- eslint/control-structure-spacing.js | 46 +- extensions/exports_something.js | 2 +- extensions/metering/routes/usage.js | 10 +- mods/mods_available/example-singlefile.js | 10 +- mods/mods_available/example/main.js | 10 +- .../kdmod/CustomPuterService.js | 21 +- mods/mods_available/kdmod/ShareTestService.js | 26 +- .../kdmod/data/sharetest_scenarios.js | 28 +- mods/mods_available/kdmod/gui/main.js | 38 +- mods/mods_available/kdmod/module.js | 2 +- package.json | 6 +- src/backend-core-0/src/pdim/validation.js | 32 +- src/backend-core-0/webpack.config.js | 1 - src/backend/exports.js | 60 +- src/backend/src/DatabaseModule.js | 6 +- src/backend/src/Extension.js | 50 +- src/backend/src/ExtensionModule.js | 14 +- src/backend/src/ExtensionService.js | 16 +- src/backend/src/Kernel.js | 170 +- src/backend/src/LocalDiskStorageModule.js | 4 +- src/backend/src/MemoryStorageModule.js | 2 +- src/backend/src/ThirdPartyDriversModule.js | 2 +- src/backend/src/annotatedobjects.js | 4 +- src/backend/src/api/PathOrUIDValidator.js | 22 +- src/backend/src/api/api_error_handler.js | 86 +- src/backend/src/api/eggspress.js | 8 +- src/backend/src/api/filesystem/FSNodeParam.js | 18 +- src/backend/src/api/filesystem/FlagParam.js | 4 +- src/backend/src/api/filesystem/StringParam.js | 4 +- src/backend/src/api/filesystem/UserParam.js | 2 +- src/backend/src/app.js | 2 +- src/backend/src/boot/BootLogger.js | 18 +- src/backend/src/boot/RuntimeEnvironment.js | 142 +- src/backend/src/boot/default_config.js | 4 +- src/backend/src/codex/CodeUtil.js | 4 +- src/backend/src/codex/Sequence.js | 34 +- src/backend/src/config.js | 58 +- src/backend/src/config/ConfigLoader.js | 14 +- src/backend/src/config/deep_proto_merge.js | 16 +- src/backend/src/data/hardcoded-permissions.js | 10 +- src/backend/src/definitions/Library.js | 2 +- src/backend/src/definitions/SimpleEntity.js | 6 +- src/backend/src/entities/Group.js | 8 +- src/backend/src/errors/TechnicalError.js | 2 +- src/backend/src/errors/error_help_details.js | 64 +- src/backend/src/extension/RuntimeModule.js | 4 +- .../src/extension/RuntimeModuleRegistry.js | 10 +- src/backend/src/filesystem/ECMAP.js | 48 +- src/backend/src/filesystem/FSNodeContext.js | 174 +- .../src/filesystem/batch/BatchExecutor.js | 20 +- src/backend/src/filesystem/batch/commands.js | 56 +- .../src/filesystem/definitions/ts/fsentry.js | 4 +- .../filesystem/hl_operations/definitions.js | 5 +- .../src/filesystem/hl_operations/hl_copy.js | 44 +- .../filesystem/hl_operations/hl_data_read.js | 15 +- .../src/filesystem/hl_operations/hl_mkdir.js | 72 +- .../src/filesystem/hl_operations/hl_mklink.js | 14 +- .../filesystem/hl_operations/hl_mkshortcut.js | 22 +- .../hl_operations/hl_name_search.js | 32 +- .../src/filesystem/hl_operations/hl_read.js | 25 +- .../filesystem/hl_operations/hl_readdir.js | 33 +- .../src/filesystem/hl_operations/hl_remove.js | 14 +- .../src/filesystem/hl_operations/hl_stat.js | 30 +- src/backend/src/filesystem/lib/PuterPath.js | 12 +- .../filesystem/ll_operations/definitions.js | 7 +- .../src/filesystem/ll_operations/ll_copy.js | 4 +- .../filesystem/ll_operations/ll_listusers.js | 21 +- .../src/filesystem/ll_operations/ll_move.js | 4 +- .../src/filesystem/ll_operations/ll_read.js | 12 +- .../filesystem/ll_operations/ll_readdir.js | 26 +- .../filesystem/ll_operations/ll_readshares.js | 16 +- .../src/filesystem/ll_operations/ll_rmdir.js | 40 +- .../src/filesystem/ll_operations/ll_rmnode.js | 4 +- .../src/filesystem/ll_operations/ll_write.js | 14 +- src/backend/src/filesystem/node/selectors.js | 12 +- src/backend/src/filesystem/node/states.js | 9 +- .../storage/UploadProgressTracker.js | 4 +- .../storage_a/LocalDiskStorageStrategy.js | 10 +- src/backend/src/filesystem/validation.js | 16 +- src/backend/src/fun/dev-console-ui-utils.js | 2 +- src/backend/src/fun/logos.js | 18 +- src/backend/src/helpers.js | 986 +- src/backend/src/html_footer.js | 94 +- src/backend/src/html_head.js | 126 +- src/backend/src/index.js | 11 +- src/backend/src/kernel/modutil.js | 10 +- src/backend/src/libraries/ArrayUtil.js | 42 +- src/backend/src/libraries/LibTypeTagged.js | 82 +- src/backend/src/middleware/abuse.js | 6 +- src/backend/src/middleware/anticsrf.js | 16 +- src/backend/src/middleware/auth.js | 16 +- src/backend/src/middleware/auth2.js | 2 +- .../src/middleware/configurable_auth.js | 40 +- src/backend/src/middleware/featureflag.js | 14 +- src/backend/src/middleware/measure.js | 36 +- src/backend/src/middleware/subdomain.js | 6 +- src/backend/src/middleware/verified.js | 10 +- .../src/modules/apps/AppIconService.js | 72 +- .../src/modules/apps/AppInformationService.js | 532 +- src/backend/src/modules/apps/AppsModule.js | 12 +- .../src/modules/apps/OldAppNameService.js | 43 +- .../src/modules/apps/ProtectedAppService.js | 25 +- .../modules/apps/RecommendedAppsService.js | 29 +- .../src/modules/apps/default-app-icon.js | 8 +- .../src/modules/broadcast/BroadcastModule.js | 10 +- .../src/modules/broadcast/BroadcastService.js | 45 +- .../modules/broadcast/connection/BaseLink.js | 12 +- .../src/modules/broadcast/connection/CLink.js | 36 +- .../broadcast/connection/KeyPairHelper.js | 43 +- .../src/modules/broadcast/connection/SLink.js | 46 +- .../src/modules/captcha/CaptchaModule.js | 10 +- .../captcha/middleware/captcha-middleware.js | 50 +- .../captcha/services/CaptchaService.js | 336 +- .../src/modules/core/AlarmService.d.ts | 6 +- src/backend/src/modules/core/AlarmService.js | 80 +- .../src/modules/core/ContextService.js | 14 +- src/backend/src/modules/core/Core2Module.js | 44 +- src/backend/src/modules/core/ErrorService.js | 14 +- .../src/modules/core/ExpectationService.js | 17 +- src/backend/src/modules/core/LogService.js | 219 +- src/backend/src/modules/core/PagerService.js | 14 +- .../src/modules/core/ParameterService.js | 43 +- .../src/modules/core/ProcessEventService.js | 8 +- .../src/modules/core/ServerHealthService.js | 64 +- src/backend/src/modules/core/lib/__lib__.js | 8 +- src/backend/src/modules/core/lib/expect.js | 18 +- .../src/modules/core/lib/identifier.js | 35 +- src/backend/src/modules/core/lib/linux.js | 3 +- src/backend/src/modules/core/lib/log.js | 7 +- src/backend/src/modules/core/lib/stdio.js | 10 +- .../modules/development/DevelopmentModule.js | 14 +- .../development/LocalTerminalService.js | 73 +- src/backend/src/modules/dns/DNSModule.js | 4 +- src/backend/src/modules/dns/DNSService.js | 63 +- .../src/modules/domain/DomainModule.js | 2 +- .../domain/DomainVerificationService.js | 12 +- .../src/modules/domain/TXTVerifyService.js | 26 +- .../EntityStoreInterfaceService.js | 42 +- .../modules/entitystore/EntityStoreModule.js | 16 +- .../src/modules/hostos/HostOSModule.js | 2 +- .../src/modules/hostos/ProcessService.js | 24 +- .../src/modules/internet/InternetModule.js | 6 +- .../src/modules/internet/WispRelayService.js | 2 +- .../kvstore/KVStoreInterfaceService.js | 2 +- .../src/modules/kvstore/KVStoreModule.js | 16 +- .../src/modules/perfmon/PerfMonModule.js | 14 +- .../src/modules/perfmon/TelemetryService.js | 35 +- .../src/modules/puterai/AIInterfaceService.js | 55 +- .../src/modules/puterai/AITestModeService.js | 11 +- .../src/modules/puterai/AWSPollyService.js | 36 +- .../src/modules/puterai/AWSTextractService.js | 20 +- .../src/modules/puterai/DeepSeekService.js | 14 +- .../src/modules/puterai/FakeChatService.js | 56 +- .../puterai/GeminiImageGenerationService.js | 36 +- .../src/modules/puterai/GeminiService.js | 14 +- .../src/modules/puterai/GroqAIService.js | 12 +- .../src/modules/puterai/MistralAIService.js | 40 +- .../puterai/OpenAIImageGenerationService.js | 42 +- .../puterai/OpenAISpeechToTextService.js | 46 +- .../src/modules/puterai/OpenAITTSService.js | 24 +- .../puterai/OpenAIVideoGenerationService.js | 46 +- .../src/modules/puterai/PuterAIModule.js | 8 +- .../puterai/TogetherImageGenerationService.js | 36 +- .../puterai/TogetherVideoGenerationService.js | 26 +- .../puterai/UsageLimitedChatService.js | 8 +- src/backend/src/modules/puterai/XAIService.js | 14 +- .../puterai/experiment/stream_claude.js | 4 +- .../puterai/experiment/stream_openai.js | 5 +- .../src/modules/puterai/lib/AsModeration.js | 40 +- .../modules/puterai/lib/FunctionCalling.js | 36 +- .../src/modules/puterai/lib/Messages.js | 34 +- .../src/modules/puterai/lib/Streaming.js | 20 +- .../src/modules/puterai/lib/messages.test.js | 64 +- .../src/modules/puterai/samples/claude-1.js | 26 +- .../modules/puterai/samples/claude-tools-1.js | 30 +- .../src/modules/puterai/samples/openai-1.js | 34 +- .../modules/puterai/samples/openai-tools-1.js | 54 +- .../src/modules/puterfs/MountpointService.js | 16 +- .../src/modules/puterfs/ResourceService.js | 26 +- .../src/modules/puterfs/SizeService.js | 50 +- .../puterfs/customfs/MemoryFSProvider.js | 132 +- .../puterfs/customfs/MemoryFSService.js | 18 +- .../ComplainAboutVersionsService.js | 16 +- .../modules/selfhosted/DefaultUserService.js | 149 +- .../modules/selfhosted/DevCreditService.js | 21 +- .../modules/selfhosted/DevWatcherService.js | 35 +- .../src/modules/selfhosted/MinLogService.js | 36 +- .../modules/selfhosted/SelfHostedModule.js | 16 +- .../modules/selfhosted/SelfhostedService.js | 20 +- .../selfhosted/ServeSingeFileService.js | 6 +- .../selfhosted/ServeStaticFilesService.js | 2 +- .../src/modules/template/TemplateModule.js | 14 +- .../src/modules/template/TemplateService.js | 37 +- .../src/modules/template/lib/__lib__.js | 8 +- .../src/modules/template/lib/hello_world.js | 13 +- .../modules/test-config/TestConfigModule.js | 2 +- .../test-config/TestConfigReadService.js | 7 +- .../test-config/TestConfigUpdateService.js | 4 +- .../test-drivers/TestAssetHostService.js | 17 +- .../modules/test-drivers/TestDriversModule.js | 16 +- .../modules/test-drivers/TestImageService.js | 42 +- .../src/modules/web/APIErrorService.js | 22 +- .../src/modules/web/SocketioService.js | 25 +- src/backend/src/modules/web/WebModule.js | 30 +- .../src/modules/web/WebServerService.d.ts | 4 +- .../src/modules/web/WebServerService.js | 60 +- src/backend/src/modules/web/lib/__lib__.js | 12 +- .../src/modules/web/lib/api_error_handler.js | 82 +- src/backend/src/monitor/PerformanceMonitor.js | 38 +- src/backend/src/om/IdentifierUtil.js | 14 +- src/backend/src/om/definitions/Mapping.js | 16 +- src/backend/src/om/definitions/PropType.js | 6 +- src/backend/src/om/definitions/Property.js | 8 +- src/backend/src/om/entitystorage/AppES.js | 131 +- .../src/om/entitystorage/AppLimitedES.js | 12 +- src/backend/src/om/entitystorage/BaseES.js | 13 +- src/backend/src/om/entitystorage/ESBuilder.js | 4 +- src/backend/src/om/entitystorage/Entity.js | 10 +- .../src/om/entitystorage/MaxLimitES.js | 4 +- .../src/om/entitystorage/NotificationES.js | 10 +- .../src/om/entitystorage/OwnerLimitedES.js | 11 +- .../src/om/entitystorage/ProtectedAppES.js | 48 +- .../src/om/entitystorage/ReadOnlyES.js | 4 +- src/backend/src/om/entitystorage/SQLES.js | 58 +- .../src/om/entitystorage/SetOwnerES.js | 12 +- .../src/om/entitystorage/SubdomainES.js | 36 +- .../src/om/entitystorage/ValidationES.js | 15 +- .../om/entitystorage/WriteByOwnerOnlyES.js | 16 +- src/backend/src/om/mappings/access-token.js | 2 +- src/backend/src/om/mappings/app.js | 25 +- src/backend/src/om/mappings/notification.js | 8 +- src/backend/src/om/mappings/subdomain.js | 13 +- src/backend/src/om/proptypes/__all__.js | 58 +- src/backend/src/om/query/query.js | 12 +- .../src/polyfill/to-string-higher-radix.js | 10 +- .../bootstrap-5.1.3/js/bootstrap.bundle.js | 10451 ++++++++-------- .../js/bootstrap.bundle.min.js | 1150 +- .../bootstrap-5.1.3/js/bootstrap.esm.js | 6110 +++++---- .../bootstrap-5.1.3/js/bootstrap.esm.min.js | 825 +- .../assets/bootstrap-5.1.3/js/bootstrap.js | 7363 ++++++----- .../bootstrap-5.1.3/js/bootstrap.min.js | 833 +- src/backend/src/public/assets/js/app.js | 116 +- .../js/jquery-3.6.0/jquery-3.6.0.min.js | 1802 ++- .../src/public/assets/js/timeago.min.js | 38 +- src/backend/src/routers/_default.js | 309 +- src/backend/src/routers/apps.js | 223 +- .../src/routers/auth/app-uid-from-origin.js | 6 +- .../routers/auth/check-app-acl.endpoint.js | 42 +- src/backend/src/routers/auth/check-app.js | 13 +- src/backend/src/routers/auth/configure-2fa.js | 28 +- .../src/routers/auth/create-access-token.js | 16 +- .../src/routers/auth/delete-own-user.js | 18 +- .../src/routers/auth/get-user-app-token.js | 22 +- src/backend/src/routers/auth/grant-dev-app.js | 17 +- .../src/routers/auth/grant-user-app.js | 16 +- .../src/routers/auth/grant-user-group.js | 17 +- .../src/routers/auth/grant-user-user.js | 15 +- src/backend/src/routers/auth/list-sessions.js | 6 +- .../src/routers/auth/revoke-dev-app.js | 18 +- .../src/routers/auth/revoke-session.js | 15 +- .../src/routers/auth/revoke-user-app.js | 17 +- .../src/routers/auth/revoke-user-group.js | 18 +- .../src/routers/auth/revoke-user-user.js | 14 +- src/backend/src/routers/change_email.js | 32 +- src/backend/src/routers/change_username.js | 56 +- src/backend/src/routers/confirm-email.js | 49 +- src/backend/src/routers/contactUs.js | 52 +- src/backend/src/routers/delete-site.js | 30 +- src/backend/src/routers/df.js | 60 +- src/backend/src/routers/down.js | 54 +- src/backend/src/routers/drivers/call.js | 26 +- .../src/routers/drivers/list-interfaces.js | 14 +- src/backend/src/routers/drivers/usage.js | 15 +- src/backend/src/routers/drivers/xd.js | 7 +- src/backend/src/routers/file.js | 30 +- .../filesystem_api/batch/PathResolver.js | 6 +- .../src/routers/filesystem_api/batch/all.js | 56 +- .../src/routers/filesystem_api/cache.js | 2 +- .../src/routers/filesystem_api/copy.js | 8 +- .../src/routers/filesystem_api/delete.js | 26 +- .../src/routers/filesystem_api/mkdir.js | 38 +- .../src/routers/filesystem_api/move.js | 8 +- .../src/routers/filesystem_api/read.js | 64 +- .../src/routers/filesystem_api/readdir.js | 4 +- .../src/routers/filesystem_api/rename.js | 118 +- .../src/routers/filesystem_api/search.js | 12 +- .../src/routers/filesystem_api/stat.js | 4 +- .../src/routers/filesystem_api/token-read.js | 65 +- .../src/routers/filesystem_api/touch.js | 190 +- .../src/routers/filesystem_api/write.js | 32 +- src/backend/src/routers/get-dev-profile.js | 28 +- src/backend/src/routers/get-launch-apps.js | 23 +- .../src/routers/get-launch-apps.test.js | 152 +- src/backend/src/routers/healthcheck.js | 12 +- src/backend/src/routers/hosting/puter-site.js | 200 +- src/backend/src/routers/itemMetadata.js | 88 +- src/backend/src/routers/kvstore/clearItems.js | 16 +- src/backend/src/routers/kvstore/getItem.js | 8 +- src/backend/src/routers/kvstore/listItems.js | 18 +- src/backend/src/routers/kvstore/setItem.js | 51 +- src/backend/src/routers/login.js | 262 +- src/backend/src/routers/logout.js | 18 +- src/backend/src/routers/open_item.js | 20 +- src/backend/src/routers/passwd.js | 64 +- src/backend/src/routers/rao.js | 71 +- src/backend/src/routers/remove-site-dir.js | 60 +- src/backend/src/routers/removeItem.js | 49 +- src/backend/src/routers/save_account.js | 174 +- .../src/routers/send-pass-recovery-email.js | 100 +- src/backend/src/routers/set-desktop-bg.js | 34 +- .../src/routers/set-pass-using-token.js | 58 +- src/backend/src/routers/set_layout.js | 68 +- src/backend/src/routers/set_sort_by.js | 78 +- src/backend/src/routers/sign.js | 66 +- src/backend/src/routers/signup.js | 20 +- src/backend/src/routers/sites.js | 48 +- src/backend/src/routers/suggest_apps.js | 54 +- src/backend/src/routers/test.js | 8 +- .../src/routers/update-taskbar-items.js | 42 +- .../routers/user-protected/change-email.js | 52 +- .../routers/user-protected/change-password.js | 22 +- .../src/routers/user-protected/disable-2fa.js | 10 +- .../src/routers/verify-pass-recovery-token.js | 22 +- src/backend/src/routers/version.js | 2 +- src/backend/src/routers/writeFile.js | 52 +- src/backend/src/routers/writeFile/copy.js | 10 +- src/backend/src/routers/writeFile/delete.js | 13 +- src/backend/src/routers/writeFile/mkdir.js | 30 +- src/backend/src/routers/writeFile/move.js | 22 +- src/backend/src/routers/writeFile/rename.js | 54 +- src/backend/src/routers/writeFile/trash.js | 31 +- src/backend/src/routers/writeFile/write.js | 32 +- .../routers/writeFile/writeFile_handlers.js | 8 +- .../src/services/AWSSecretsPopulator.js | 39 +- src/backend/src/services/AnomalyService.js | 25 +- src/backend/src/services/BaseService.js | 18 +- src/backend/src/services/BootScriptService.js | 20 +- src/backend/src/services/ChatAPIService.js | 22 +- .../src/services/ChatAPIService.test.js | 66 +- src/backend/src/services/CleanEmailService.js | 22 +- .../src/services/ClientOperationService.js | 18 +- src/backend/src/services/CommandService.js | 47 +- src/backend/src/services/CommentService.js | 62 +- .../services/ConfigurableCountingService.js | 59 +- src/backend/src/services/Container.js | 61 +- .../src/services/ContextInitService.js | 11 +- .../src/services/DetailProviderService.js | 17 +- src/backend/src/services/DevConsoleService.js | 129 +- src/backend/src/services/DevSocketService.js | 17 +- src/backend/src/services/DevTODService.js | 30 +- src/backend/src/services/EmailService.js | 37 +- src/backend/src/services/EngPortalService.js | 8 +- .../src/services/EntityStoreService.js | 95 +- src/backend/src/services/EntriService.js | 189 +- src/backend/src/services/EventService.js | 36 +- .../src/services/FeatureFlagService.js | 31 +- .../src/services/FilesystemAPIService.js | 53 +- src/backend/src/services/GetUserService.js | 18 +- src/backend/src/services/HelloWorldService.js | 19 +- .../src/services/HostDiskUsageService.js | 49 +- src/backend/src/services/HostnameService.js | 12 +- src/backend/src/services/KernelInfoService.js | 58 +- src/backend/src/services/LockService.js | 55 +- .../MakeProdDebuggingLessAwfulService.js | 24 +- .../src/services/MemoryStorageService.js | 6 +- .../MeteringServiceWrapper.mjs | 2 +- .../costMaps/openaiImageCostMap.ts | 18 +- .../src/services/MeteringService/types.ts | 2 +- .../src/services/NotificationService.js | 86 +- .../src/services/NullDevConsoleService.js | 25 +- .../src/services/OperationTraceService.js | 80 +- .../src/services/PermissionAPIService.js | 14 +- src/backend/src/services/PuterAPIService.js | 112 +- .../src/services/PuterHomepageService.js | 66 +- src/backend/src/services/PuterSiteService.js | 65 +- .../src/services/PuterVersionService.js | 27 +- .../src/services/ReferralCodeService.js | 66 +- .../services/RefreshAssociationsService.js | 20 +- src/backend/src/services/RegistrantService.js | 19 +- src/backend/src/services/RegistryService.js | 15 +- .../src/services/RequestMeasureService.js | 5 +- src/backend/src/services/SNSService.js | 51 +- src/backend/src/services/SUService.d.ts | 4 +- src/backend/src/services/SUService.js | 8 +- src/backend/src/services/ScriptService.js | 26 +- src/backend/src/services/ServeGUIService.js | 10 +- src/backend/src/services/ServicePatch.js | 19 +- src/backend/src/services/SessionService.js | 71 +- src/backend/src/services/ShareService.js | 187 +- src/backend/src/services/ShutdownService.js | 11 +- src/backend/src/services/StorageService.js | 3 +- .../src/services/StrategizedService.js | 20 +- src/backend/src/services/SystemDataService.js | 31 +- .../src/services/SystemValidationService.js | 15 +- src/backend/src/services/ThreadService.js | 134 +- src/backend/src/services/TraceService.js | 2 +- src/backend/src/services/UserService.d.ts | 8 +- src/backend/src/services/UserService.js | 2 +- .../src/services/VerifiedGroupService.js | 8 +- src/backend/src/services/WSPushService.js | 154 +- .../src/services/WebDAV/WebDAVService.js | 22 +- src/backend/src/services/WebDAV/lockStore.mjs | 12 +- .../services/WebDAV/methodHandlers/COPY.mjs | 18 +- .../services/WebDAV/methodHandlers/DELETE.mjs | 6 +- .../WebDAV/methodHandlers/HEAD_GET.mjs | 14 +- .../services/WebDAV/methodHandlers/LOCK.mjs | 4 +- .../services/WebDAV/methodHandlers/MKCOL.mjs | 8 +- .../services/WebDAV/methodHandlers/MOVE.mjs | 16 +- .../WebDAV/methodHandlers/OPTIONS.mjs | 12 +- .../WebDAV/methodHandlers/PROPFIND.mjs | 4 +- .../WebDAV/methodHandlers/PROPPATCH.mjs | 4 +- .../services/WebDAV/methodHandlers/PUT.mjs | 6 +- .../services/WebDAV/methodHandlers/UNLOCK.mjs | 6 +- src/backend/src/services/WispService.js | 26 +- .../abuse-prevention/AuthAuditService.js | 49 +- .../abuse-prevention/EdgeRateLimitService.js | 35 +- .../abuse-prevention/IdentificationService.js | 20 +- src/backend/src/services/auth/ACLService.js | 22 +- src/backend/src/services/auth/Actor.d.ts | 12 +- src/backend/src/services/auth/Actor.js | 40 +- .../src/services/auth/AntiCSRFService.js | 45 +- src/backend/src/services/auth/AuthService.js | 184 +- src/backend/src/services/auth/GroupService.js | 145 +- src/backend/src/services/auth/OTPService.js | 15 +- .../src/services/auth/PermissionService.js | 88 +- .../auth/PermissionShortcutService.js | 2 +- .../src/services/auth/PreAuthService.js | 4 +- src/backend/src/services/auth/TokenService.js | 24 +- .../src/services/auth/VirtualGroupService.js | 25 +- .../src/services/auth/permissionUtils.mjs | 42 +- .../database/BaseDatabaseAccessService.js | 30 +- .../database/SqliteDatabaseAccessService.js | 26 +- .../src/services/database/constructs.js | 2 +- .../sqlite_setup/0025_system-user.dbmig.js | 60 +- .../sqlite_setup/0026_user-groups.dbmig.js | 24 +- .../sqlite_setup/0027_emulator-app.dbmig.js | 20 +- .../src/services/drivers/CoercionService.js | 66 +- .../src/services/drivers/DriverError.js | 3 +- .../drivers/DriverUsagePolicyService.js | 46 +- .../src/services/drivers/FileFacade.js | 31 +- .../src/services/drivers/meta/Construct.js | 23 +- .../src/services/drivers/meta/Runtime.js | 20 +- src/backend/src/services/drivers/types.js | 76 +- .../src/services/file-cache/FileTracker.js | 16 +- src/backend/src/services/fs/FSLockService.js | 53 +- .../information/InformationService.js | 41 +- .../periodic/FSEntryMigrateService.js | 78 +- .../repositories/DBKVStore/DBKVStore.ts | 40 +- .../services/repositories/DBKVStore/index.mjs | 4 +- .../src/services/sla/RateLimitService.js | 52 +- src/backend/src/services/sla/SLAService.js | 4 +- .../thumbnails/HTTPThumbnailService.js | 133 +- .../thumbnails/NAPIThumbnailService.js | 17 +- .../thumbnails/PureJSThumbnailService.js | 14 +- .../web/UserProtectedEndpointsService.js | 40 +- .../src/services/worker/WorkerService.js | 333 +- src/backend/src/services/worker/src/index.js | 1 - .../src/services/worker/src/s2w-router.js | 114 +- .../src/services/worker/webpack.config.js | 34 +- .../worker/workerUtils/cloudflareDeploy.js | 88 +- .../services/worker/workerUtils/nameUtils.js | 12 +- .../services/worker/workerUtils/puterUtils.js | 14 +- .../structured/sequence/scan-permission.mjs | 12 +- src/backend/src/structured/sequence/share.js | 34 +- .../sequence/share/process_recipients.js | 20 +- .../sequence/share/process_shares.js | 42 +- .../src/structured/sequence/share/validate.js | 18 +- .../src/traits/AssignableMethodsFeature.js | 2 +- .../src/traits/AsyncProviderFeature.js | 2 +- src/backend/src/traits/ChannelFeature.js | 10 +- src/backend/src/traits/ContextAwareFeature.js | 2 +- src/backend/src/traits/OtelFeature.js | 8 +- src/backend/src/traits/SyncFeature.js | 2 +- .../src/traits/WeakConstructorFeature.js | 2 +- .../src/unstructured/permission-scan-lib.js | 10 +- .../src/unstructured/permission-scanners.js | 30 +- src/backend/src/user-mig.js | 18 +- src/backend/src/util/asyncutil.js | 6 +- src/backend/src/util/configutil.js | 2 +- src/backend/src/util/consolelog.js | 12 +- src/backend/src/util/context.js | 25 +- src/backend/src/util/datautil.js | 2 +- src/backend/src/util/debugutil.js | 14 +- src/backend/src/util/errorutil.js | 10 +- src/backend/src/util/expressutil.js | 6 +- src/backend/src/util/fnutil.js | 2 +- src/backend/src/util/fuzz.js | 82 +- src/backend/src/util/gcutil.js | 3 +- src/backend/src/util/hl_types.js | 8 +- src/backend/src/util/hl_types.test.js | 4 +- src/backend/src/util/identifier.js | 34 +- src/backend/src/util/langutil.js | 2 +- src/backend/src/util/lockutil.js | 13 +- src/backend/src/util/multivalue.js | 6 +- src/backend/src/util/objutil.js | 4 +- src/backend/src/util/opmath.js | 16 +- src/backend/src/util/opmath.test.js | 6 +- src/backend/src/util/otelutil.js | 10 +- src/backend/src/util/pathutil.js | 12 +- src/backend/src/util/retryutil.js | 4 +- src/backend/src/util/stdioutil.js | 9 +- src/backend/src/util/streamutil.js | 97 +- src/backend/src/util/structutil.js | 14 +- src/backend/src/util/urlutil.js | 4 +- src/backend/src/util/uuidfpe.js | 32 +- src/backend/src/util/validutil.js | 2 +- src/backend/src/util/versionutil.js | 10 +- src/backend/src/util/workutil.js | 12 +- .../integration/extension-integration.test.js | 140 +- src/backend/tools/test.js | 82 +- src/dev-center/js/apps.js | 208 +- src/dev-center/js/dev-center.js | 204 +- src/dev-center/js/images.js | 14 +- src/dev-center/js/libs/html-entities.js | 53 +- src/dev-center/js/libs/jquery.dragster.js | 18 +- src/dev-center/js/libs/slugify.js | 104 +- src/dev-center/js/websites.js | 231 +- src/dev-center/js/workers.js | 202 +- src/emulator/src/main.js | 137 +- src/emulator/webpack.config.js | 20 +- src/gui/build.js | 2 +- src/gui/dev-server.js | 30 +- src/gui/src/IPC.js | 894 +- src/gui/src/UI/Components/Button.js | 9 +- src/gui/src/UI/Components/CodeEntryView.js | 83 +- .../src/UI/Components/ConfirmationsView.js | 5 +- src/gui/src/UI/Components/Flexer.js | 3 +- src/gui/src/UI/Components/JustHTML.js | 5 +- src/gui/src/UI/Components/PasswordEntry.js | 15 +- src/gui/src/UI/Components/QRCode.js | 11 +- .../UI/Components/RecoveryCodeEntryView.js | 3 +- .../src/UI/Components/RecoveryCodesView.js | 8 +- src/gui/src/UI/Components/StepHeading.js | 5 +- src/gui/src/UI/Components/StepView.js | 11 +- src/gui/src/UI/PuterDialog.js | 22 +- src/gui/src/UI/Settings/UITabAbout.js | 24 +- src/gui/src/UI/Settings/UITabAccount.js | 118 +- src/gui/src/UI/Settings/UITabLanguage.js | 22 +- .../src/UI/Settings/UITabPersonalization.js | 10 +- src/gui/src/UI/Settings/UITabSecurity.js | 83 +- src/gui/src/UI/Settings/UITabUsage.js | 57 +- .../src/UI/Settings/UIWindowChangeEmail.js | 84 +- .../Settings/UIWindowConfirmUserDeletion.js | 34 +- .../Settings/UIWindowFinalizeUserDeletion.js | 94 +- src/gui/src/UI/Settings/UIWindowSettings.js | 122 +- src/gui/src/UI/UIAlert.js | 84 +- src/gui/src/UI/UIColorPickerWidget.js | 67 +- src/gui/src/UI/UIComponentWindow.js | 4 +- src/gui/src/UI/UIContextMenu.js | 345 +- src/gui/src/UI/UIDesktop.js | 1184 +- src/gui/src/UI/UIElement.js | 30 +- src/gui/src/UI/UIItem.js | 1220 +- src/gui/src/UI/UINotification.js | 87 +- src/gui/src/UI/UIPopover.js | 87 +- src/gui/src/UI/UIPrompt.js | 70 +- src/gui/src/UI/UITaskbar.js | 436 +- src/gui/src/UI/UITaskbarItem.js | 494 +- src/gui/src/UI/UIWindow.js | 2866 +++-- src/gui/src/UI/UIWindow2FASetup.js | 54 +- src/gui/src/UI/UIWindowChangePassword.js | 96 +- src/gui/src/UI/UIWindowChangeUsername.js | 72 +- src/gui/src/UI/UIWindowClaimReferral.js | 32 +- src/gui/src/UI/UIWindowColorPicker.js | 66 +- src/gui/src/UI/UIWindowDesktopBGSettings.js | 184 +- .../UI/UIWindowEmailConfirmationRequired.js | 194 +- src/gui/src/UI/UIWindowFeedback.js | 70 +- src/gui/src/UI/UIWindowFontPicker.js | 72 +- src/gui/src/UI/UIWindowItemProperties.js | 141 +- src/gui/src/UI/UIWindowLogin.js | 285 +- src/gui/src/UI/UIWindowLoginInProgress.js | 36 +- src/gui/src/UI/UIWindowManageSessions.js | 83 +- src/gui/src/UI/UIWindowMyWebsites.js | 145 +- src/gui/src/UI/UIWindowNewPassword.js | 113 +- src/gui/src/UI/UIWindowProgress.js | 88 +- src/gui/src/UI/UIWindowPublishWebsite.js | 308 +- src/gui/src/UI/UIWindowPublishWorker.js | 143 +- src/gui/src/UI/UIWindowQR.js | 26 +- src/gui/src/UI/UIWindowRecoverPassword.js | 78 +- src/gui/src/UI/UIWindowRefer.js | 68 +- src/gui/src/UI/UIWindowRequestPermission.js | 101 +- src/gui/src/UI/UIWindowSaveAccount.js | 176 +- src/gui/src/UI/UIWindowSearch.js | 144 +- src/gui/src/UI/UIWindowSessionList.js | 100 +- src/gui/src/UI/UIWindowShare.js | 414 +- src/gui/src/UI/UIWindowSignup.js | 283 +- src/gui/src/UI/UIWindowTaskManager.js | 102 +- src/gui/src/UI/UIWindowThemeDialog.js | 4 +- src/gui/src/UI/UIWindowWelcome.js | 52 +- src/gui/src/definitions.js | 48 +- src/gui/src/extensions/groups-manager.js | 14 +- .../extensions/modify-user-options-menu.js | 18 +- src/gui/src/globals.js | 106 +- src/gui/src/helpers.js | 2246 ++-- .../src/helpers/check_password_strength.js | 13 +- src/gui/src/helpers/content_type_to_icon.js | 134 +- .../determine_active_container_parent.js | 16 +- src/gui/src/helpers/download.js | 62 +- .../src/helpers/fixedEncodeURIComponent.js | 14 +- .../helpers/get_html_element_from_options.js | 105 +- src/gui/src/helpers/globToRegExp.js | 170 +- src/gui/src/helpers/item_icon.js | 206 +- src/gui/src/helpers/launch_app.js | 251 +- src/gui/src/helpers/new_context_menu_item.js | 94 +- src/gui/src/helpers/open_item.js | 183 +- src/gui/src/helpers/refresh_item_container.js | 120 +- src/gui/src/helpers/socialLink.js | 96 +- src/gui/src/helpers/truncate_filename.js | 14 +- .../helpers/update_last_touch_coordinates.js | 12 +- src/gui/src/helpers/update_mouse_position.js | 60 +- .../helpers/update_title_based_on_uploads.js | 24 +- src/gui/src/helpers/update_username_in_gui.js | 96 +- src/gui/src/i18n/i18n.js | 30 +- src/gui/src/i18n/i18nChangeLanguage.js | 5 +- src/gui/src/i18n/translations/ar.js | 972 +- src/gui/src/i18n/translations/bg.js | 1028 +- src/gui/src/i18n/translations/bn.js | 916 +- src/gui/src/i18n/translations/da.js | 1008 +- src/gui/src/i18n/translations/de.js | 611 +- src/gui/src/i18n/translations/emoji.js | 182 +- src/gui/src/i18n/translations/en.js | 419 +- src/gui/src/i18n/translations/es.js | 910 +- src/gui/src/i18n/translations/fa.js | 972 +- src/gui/src/i18n/translations/fi.js | 1046 +- src/gui/src/i18n/translations/fr.js | 403 +- src/gui/src/i18n/translations/he.js | 936 +- src/gui/src/i18n/translations/hi.js | 613 +- src/gui/src/i18n/translations/hu.js | 884 +- src/gui/src/i18n/translations/hy.js | 1010 +- src/gui/src/i18n/translations/id.js | 954 +- src/gui/src/i18n/translations/ig.js | 951 +- src/gui/src/i18n/translations/it.js | 980 +- src/gui/src/i18n/translations/ja.js | 617 +- src/gui/src/i18n/translations/ko.js | 976 +- src/gui/src/i18n/translations/ku.js | 952 +- src/gui/src/i18n/translations/ml.js | 484 +- src/gui/src/i18n/translations/nb.js | 812 +- src/gui/src/i18n/translations/nl.js | 910 +- src/gui/src/i18n/translations/nn.js | 904 +- src/gui/src/i18n/translations/pl.js | 618 +- src/gui/src/i18n/translations/pt.js | 594 +- src/gui/src/i18n/translations/ro.js | 786 +- src/gui/src/i18n/translations/ru.js | 906 +- src/gui/src/i18n/translations/sl.js | 397 +- src/gui/src/i18n/translations/sv.js | 887 +- src/gui/src/i18n/translations/ta.js | 1024 +- src/gui/src/i18n/translations/th.js | 761 +- src/gui/src/i18n/translations/tr.js | 877 +- src/gui/src/i18n/translations/translations.js | 2 +- src/gui/src/i18n/translations/ua.js | 840 +- src/gui/src/i18n/translations/ur.js | 961 +- src/gui/src/i18n/translations/vi.js | 607 +- src/gui/src/i18n/translations/zh.js | 613 +- src/gui/src/i18n/translations/zhtw.js | 608 +- src/gui/src/index.js | 36 +- src/gui/src/init_sync.js | 17 +- src/gui/src/initgui.js | 748 +- src/gui/src/keyboard.js | 434 +- .../external/jquery/jquery.js | 36 +- src/gui/src/lib/jquery-ui-1.13.2/jquery-ui.js | 26 +- src/gui/src/services/AntiCSRFService.js | 25 +- src/gui/src/services/BroadcastService.js | 14 +- src/gui/src/services/DebugService.js | 12 +- src/gui/src/services/ExecService.js | 80 +- .../src/services/ExportRegistrantService.js | 4 +- src/gui/src/services/IPCService.js | 26 +- src/gui/src/services/LaunchOnInitService.js | 6 +- src/gui/src/services/LocaleService.js | 8 +- src/gui/src/services/ProcessService.js | 4 +- src/gui/src/services/SettingsService.js | 4 +- src/gui/src/services/ThemeService.js | 34 +- src/gui/src/static-assets.js | 44 +- src/gui/src/util/Collector.js | 23 +- src/gui/src/util/Component.js | 16 +- src/gui/src/util/Placeholder.js | 4 +- src/gui/src/util/TeePromise.js | 4 +- src/gui/src/util/ValueHolder.js | 4 +- src/gui/src/util/desktop.js | 38 +- src/gui/utils.js | 263 +- src/gui/webpack.config.cjs | 10 +- src/gui/webpack/BaseConfig.cjs | 20 +- src/gui/webpack/EmitPlugin.cjs | 44 +- src/gui/webpack/libPaths.cjs | 36 +- src/parsers/exports.js | 8 +- src/parsers/parsely/exports.js | 22 +- src/parsers/parsely/parser.js | 10 +- src/parsers/parsely/parsers/combinators.js | 32 +- src/parsers/parsely/parsers/terminals.js | 34 +- src/parsers/parsely/streams.js | 20 +- src/parsers/strataparse/dsl/ParserBuilder.js | 10 +- src/parsers/strataparse/exports.js | 10 +- src/parsers/strataparse/parse.js | 6 +- .../parse_impls/StrUntilParserImpl.js | 6 +- .../strataparse/parse_impls/combinators.js | 4 +- .../strataparse/parse_impls/literal.js | 14 +- .../strataparse/parse_impls/whitespace.js | 8 +- src/parsers/strataparse/strata.js | 6 +- .../ContextSwitchingPStratumImpl.js | 12 +- .../FirstRecognizedPStratumImpl.js | 6 +- .../MergeWhitespacePStratumImpl.js | 6 +- .../strataparse/strata_impls/terminals.js | 6 +- src/phoenix/config/dev.js | 6 +- src/phoenix/config/release.js | 2 +- src/phoenix/doc/stash/SymbolParserImpl.js | 10 +- src/phoenix/src/ansi-shell/ANSIContext.js | 10 +- src/phoenix/src/ansi-shell/ANSIShell.js | 76 +- .../src/ansi-shell/ConcreteSyntaxError.js | 22 +- .../ansi-shell/arg-parsers/simple-parser.js | 17 +- .../src/ansi-shell/decorators/errors.js | 12 +- .../src/ansi-shell/ioutil/ByteWriter.js | 2 +- .../src/ansi-shell/ioutil/MemReader.js | 8 +- .../src/ansi-shell/ioutil/MemWriter.js | 11 +- .../src/ansi-shell/ioutil/NullifyWriter.js | 2 +- .../src/ansi-shell/ioutil/ProxyReader.js | 4 +- .../src/ansi-shell/ioutil/ProxyWriter.js | 8 +- .../src/ansi-shell/ioutil/SignalReader.js | 6 +- .../src/ansi-shell/ioutil/SyncLinesReader.js | 6 +- .../src/ansi-shell/parsing/PARSE_CONSTANTS.js | 6 +- .../ansi-shell/parsing/PuterShellParser.js | 14 +- .../parsing/UnquotedTokenParserImpl.js | 16 +- .../parsing/buildParserFirstHalf.js | 227 +- .../parsing/buildParserSecondHalf.js | 45 +- .../src/ansi-shell/pipeline/Coupler.js | 19 +- src/phoenix/src/ansi-shell/pipeline/Pipe.js | 6 +- .../src/ansi-shell/pipeline/Pipeline.js | 71 +- .../src/ansi-shell/pipeline/iowrappers.js | 2 +- .../src/ansi-shell/readline/history.js | 25 +- .../src/ansi-shell/readline/readline.js | 47 +- .../src/ansi-shell/readline/readtoken.js | 16 +- .../src/ansi-shell/readline/rl_comprehend.js | 14 +- .../ansi-shell/readline/rl_csi_handlers.js | 36 +- .../src/ansi-shell/readline/rl_words.js | 4 +- src/phoenix/src/context/context.js | 4 +- src/phoenix/src/main_cli.js | 11 +- src/phoenix/src/main_puter.js | 21 +- src/phoenix/src/meta/versions.js | 30 +- .../browser/node-stubs/child_process.js | 1 - .../platform/browser/node-stubs/node-pty.js | 1 - .../src/platform/browser/node-stubs/path.js | 1 - .../platform/browser/node-stubs/process.js | 1 - .../src/platform/browser/node-stubs/stream.js | 1 - src/phoenix/src/platform/node/env.js | 8 +- src/phoenix/src/platform/node/filesystem.js | 22 +- src/phoenix/src/platform/node/system.js | 4 +- src/phoenix/src/platform/puter/drivers.js | 50 +- src/phoenix/src/platform/puter/env.js | 10 +- src/phoenix/src/platform/puter/filesystem.js | 10 +- src/phoenix/src/platform/puter/system.js | 4 +- src/phoenix/src/promise.js | 10 +- src/phoenix/src/pty/NodeStdioPTT.js | 12 +- src/phoenix/src/pty/XDocumentPTT.js | 24 +- .../puter-shell/completers/FileCompleter.js | 6 +- .../src/puter-shell/coreutils/__exports__.js | 176 +- src/phoenix/src/puter-shell/coreutils/ai.js | 95 +- .../src/puter-shell/coreutils/basename.js | 18 +- src/phoenix/src/puter-shell/coreutils/cat.js | 10 +- src/phoenix/src/puter-shell/coreutils/cd.js | 6 +- .../src/puter-shell/coreutils/changelog.js | 15 +- .../src/puter-shell/coreutils/clear.js | 8 +- .../puter-shell/coreutils/concept-parser.js | 153 +- .../coreutils/coreutil_lib/echo_escapes.js | 14 +- .../coreutils/coreutil_lib/help.js | 46 +- .../coreutils/coreutil_lib/validate.js | 4 +- src/phoenix/src/puter-shell/coreutils/cp.js | 16 +- src/phoenix/src/puter-shell/coreutils/date.js | 362 +- .../src/puter-shell/coreutils/dcall.js | 6 +- .../src/puter-shell/coreutils/dirname.js | 24 +- src/phoenix/src/puter-shell/coreutils/echo.js | 20 +- src/phoenix/src/puter-shell/coreutils/env.js | 4 +- .../src/puter-shell/coreutils/errno.js | 36 +- src/phoenix/src/puter-shell/coreutils/exit.js | 10 +- .../src/puter-shell/coreutils/false.js | 4 +- src/phoenix/src/puter-shell/coreutils/grep.js | 64 +- src/phoenix/src/puter-shell/coreutils/head.js | 20 +- src/phoenix/src/puter-shell/coreutils/help.js | 26 +- src/phoenix/src/puter-shell/coreutils/jq.js | 15 +- .../src/puter-shell/coreutils/localsh.js | 14 +- .../src/puter-shell/coreutils/login.js | 8 +- src/phoenix/src/puter-shell/coreutils/ls.js | 62 +- src/phoenix/src/puter-shell/coreutils/man.js | 4 +- .../src/puter-shell/coreutils/mkdir.js | 12 +- src/phoenix/src/puter-shell/coreutils/mv.js | 8 +- .../src/puter-shell/coreutils/neofetch.js | 40 +- .../src/puter-shell/coreutils/printf.js | 364 +- .../src/puter-shell/coreutils/printhist.js | 6 +- src/phoenix/src/puter-shell/coreutils/pwd.js | 6 +- src/phoenix/src/puter-shell/coreutils/rm.js | 16 +- .../src/puter-shell/coreutils/rmdir.js | 8 +- .../src/puter-shell/coreutils/sample-data.js | 4 +- src/phoenix/src/puter-shell/coreutils/sed.js | 26 +- .../src/puter-shell/coreutils/sed/address.js | 60 +- .../src/puter-shell/coreutils/sed/command.js | 310 +- .../src/puter-shell/coreutils/sed/parser.js | 545 +- .../src/puter-shell/coreutils/sed/script.js | 110 +- .../src/puter-shell/coreutils/sed/utils.js | 2 +- .../src/puter-shell/coreutils/sleep.js | 8 +- src/phoenix/src/puter-shell/coreutils/sort.js | 66 +- src/phoenix/src/puter-shell/coreutils/tail.js | 20 +- src/phoenix/src/puter-shell/coreutils/test.js | 5 +- .../src/puter-shell/coreutils/touch.js | 12 +- src/phoenix/src/puter-shell/coreutils/true.js | 4 +- .../src/puter-shell/coreutils/txt2img.js | 4 +- .../src/puter-shell/coreutils/usages.js | 4 +- src/phoenix/src/puter-shell/coreutils/wc.js | 54 +- .../src/puter-shell/coreutils/which.js | 4 +- src/phoenix/src/puter-shell/main.js | 64 +- .../puter-shell/plugins/ChatHistoryPlugin.js | 10 +- .../providers/BuiltinCommandProvider.js | 4 +- .../providers/CompositeCommandProvider.js | 18 +- .../providers/EmuCommandProvider.js | 46 +- .../providers/PDECommandProvider.js | 54 +- .../providers/PathCommandProvider.js | 52 +- .../providers/PuterAppCommandProvider.js | 56 +- .../providers/ScriptCommandProvider.js | 6 +- src/phoenix/src/util/file.js | 16 +- src/phoenix/src/util/lang.js | 12 +- src/phoenix/src/util/path.js | 6 +- src/phoenix/src/util/statemachine.js | 28 +- src/phoenix/src/util/wrap-text.js | 36 +- src/phoenix/test.js | 11 +- src/phoenix/test/coreutils.test.js | 24 +- src/phoenix/tools/gen.js | 14 +- .../webpack-resolve-extensions-plugin.js | 45 +- src/phoenix/webpack.config.js | 33 +- src/pty/exports.js | 52 +- src/puter-js/index.d.ts | 28 +- src/puter-js/src/index.js | 182 +- src/puter-js/src/lib/APICallLogger.js | 41 +- src/puter-js/src/lib/EventListener.js | 20 +- src/puter-js/src/lib/filesystem/APIFS.js | 30 +- src/puter-js/src/lib/filesystem/CacheFS.js | 50 +- .../src/lib/filesystem/PostMessageFS.js | 22 +- .../src/lib/filesystem/definitions.js | 21 +- src/puter-js/src/lib/path.js | 951 +- .../src/lib/polyfills/localStorage.js | 63 +- src/puter-js/src/lib/polyfills/xhrshim.js | 413 +- src/puter-js/src/lib/utils.js | 295 +- src/puter-js/src/lib/xdrpc.js | 18 +- src/puter-js/src/modules/AI.js | 437 +- src/puter-js/src/modules/Apps.js | 112 +- src/puter-js/src/modules/Auth.js | 34 +- src/puter-js/src/modules/Debug.js | 2 +- src/puter-js/src/modules/Drivers.js | 82 +- src/puter-js/src/modules/FSItem.js | 126 +- src/puter-js/src/modules/FileSystem/index.js | 45 +- .../src/modules/FileSystem/operations/copy.js | 14 +- .../FileSystem/operations/deleteFSEntry.js | 26 +- .../FileSystem/operations/getReadUrl.js | 30 +- .../modules/FileSystem/operations/mkdir.js | 22 +- .../src/modules/FileSystem/operations/move.js | 24 +- .../src/modules/FileSystem/operations/read.js | 20 +- .../modules/FileSystem/operations/readdir.js | 56 +- .../modules/FileSystem/operations/rename.js | 22 +- .../src/modules/FileSystem/operations/sign.js | 63 +- .../modules/FileSystem/operations/space.js | 14 +- .../src/modules/FileSystem/operations/stat.js | 50 +- .../modules/FileSystem/operations/symlink.js | 32 +- .../modules/FileSystem/operations/upload.js | 273 +- .../modules/FileSystem/operations/write.js | 28 +- .../FileSystem/utils/getAbsolutePathForApp.js | 16 +- src/puter-js/src/modules/Hosting.js | 58 +- src/puter-js/src/modules/KV.js | 46 +- src/puter-js/src/modules/OS.js | 30 +- src/puter-js/src/modules/Perms.js | 40 +- src/puter-js/src/modules/PuterDialog.js | 94 +- src/puter-js/src/modules/Threads.js | 37 +- src/puter-js/src/modules/UI.js | 312 +- src/puter-js/src/modules/Util.js | 6 +- src/puter-js/src/modules/Workers.js | 101 +- .../src/modules/networking/PSocket.js | 76 +- src/puter-js/src/modules/networking/PTLS.js | 101 +- .../src/modules/networking/PWispHandler.js | 96 +- .../src/modules/networking/parsers.js | 218 +- .../src/modules/networking/requests.js | 145 +- src/puter-js/src/services/APIAccess.js | 18 +- src/puter-js/src/services/FSRelay.js | 12 +- src/puter-js/src/services/Filesystem.js | 16 +- src/puter-js/src/services/NoPuterYet.js | 4 +- src/puter-js/src/services/XDIncoming.js | 2 +- .../devlog/unit_test_usefulness/a.js | 95 +- .../devlog/unit_test_usefulness/b.js | 95 +- src/puter-wisp/src/exports.js | 133 +- src/puter-wisp/test/test.js | 42 +- src/putility/src/AdvancedBase.js | 4 +- src/putility/src/PosixError.js | 232 +- src/putility/src/bases/FeatureBase.js | 14 +- src/putility/src/concepts/Service.js | 27 +- src/putility/src/features/EmitterFeature.js | 28 +- .../src/features/NariMethodsFeature.js | 4 +- .../src/features/NodeModuleDIFeature.js | 4 +- .../src/features/PropertiesFeature.js | 4 +- src/putility/src/features/ServiceFeature.js | 9 +- src/putility/src/features/TopicsFeature.js | 19 +- src/putility/src/features/TraitsFeature.js | 10 +- src/putility/src/libs/context.js | 4 +- src/putility/src/libs/event.js | 4 +- src/putility/src/libs/invoker.js | 14 +- src/putility/src/libs/listener.js | 27 +- src/putility/src/libs/log.js | 107 +- src/putility/src/libs/promise.js | 29 +- src/putility/src/libs/smol.js | 1 - src/putility/src/libs/string.js | 42 +- src/putility/src/libs/time.js | 8 +- src/putility/src/system/ServiceManager.js | 20 +- src/putility/src/traits/traits.js | 2 +- src/putility/test/ServiceManager.test.js | 16 +- src/putility/test/context.test.js | 10 +- src/putility/test/event.test.js | 6 +- src/putility/test/listener.test.js | 18 +- src/putility/test/log.test.js | 10 +- src/putility/test/test.js | 6 +- src/putility/test/topics.test.js | 24 +- src/putility/test/traits.test.js | 24 +- src/terminal/config/dev.js | 4 +- src/terminal/config/release.js | 4 +- src/terminal/src/main.js | 8 +- src/terminal/src/pty/PTT.js | 8 +- src/terminal/src/pty/PTY.js | 18 +- src/terminal/src/pty/XDocumentANSIShell.js | 18 +- .../webpack-resolve-extensions-plugin.js | 45 +- src/terminal/webpack.config.js | 3 +- src/useapi/main.js | 6 +- tests/puterJsApiTests/kv.test.ts | 4 +- tests/puterJsApiTests/testUtils.ts | 20 +- 929 files changed, 56976 insertions(+), 51947 deletions(-) diff --git a/doc/contributors/extensions/gen.js b/doc/contributors/extensions/gen.js index 517b565b57..d68f5e027d 100644 --- a/doc/contributors/extensions/gen.js +++ b/doc/contributors/extensions/gen.js @@ -4,23 +4,23 @@ import events from './events.json.js'; const mdlib = {}; mdlib.h = (out, n, str) => { out(`${'#'.repeat(n)} ${str}\n\n`); -} +}; const N_START = 3; const out = str => process.stdout.write(str); for ( const event of events ) { mdlib.h(out, N_START, `\`${event.id}\``); - out(dedent(event.description) + '\n\n'); - + out(`${dedent(event.description) }\n\n`); + for ( const k in event.properties ) { const prop = event.properties[k]; mdlib.h(out, N_START + 1, `Property \`${k}\``); - out(prop.summary + '\n'); + out(`${prop.summary }\n`); out(`- **Type**: ${prop.type}\n`); out(`- **Mutability**: ${prop.mutability}\n`); if ( prop.notes ) { - out(`- **Notes**:\n`); + out('- **Notes**:\n'); for ( const note of prop.notes ) { out(` - ${note}\n`); } @@ -29,7 +29,7 @@ for ( const event of events ) { } if ( event.example ) { - mdlib.h(out, N_START + 1, `Example`); + mdlib.h(out, N_START + 1, 'Example'); out(`\`\`\`${event.example.language}\n${dedent(event.example.code)}\n\`\`\`\n`); } diff --git a/doc/contributors/extensions/manual_overrides.json.js b/doc/contributors/extensions/manual_overrides.json.js index b036fee383..fedfca263c 100644 --- a/doc/contributors/extensions/manual_overrides.json.js +++ b/doc/contributors/extensions/manual_overrides.json.js @@ -12,7 +12,7 @@ export default [ summary: 'the email being validated', notes: [ 'The email may have already been cleaned.', - ] + ], }, allow: { type: 'boolean', @@ -20,7 +20,7 @@ export default [ summary: 'whether the email is allowed', notes: [ 'If set to false, the email will be considered invalid.', - ] + ], }, }, }, @@ -44,8 +44,8 @@ export default [ measurements: data.measurements }); }); - ` - } + `, + }, }, { id: 'core.fs.create.directory', @@ -61,8 +61,8 @@ export default [ context: { type: 'Context', mutability: 'no-effect', - summary: 'current context' + summary: 'current context', }, - } + }, }, ]; \ No newline at end of file diff --git a/doc/self-hosters/config-vals.json.js b/doc/self-hosters/config-vals.json.js index 4676e3204c..11d6b6bcee 100644 --- a/doc/self-hosters/config-vals.json.js +++ b/doc/self-hosters/config-vals.json.js @@ -8,8 +8,8 @@ export default [ `, example_values: [ 'example.com', - 'subdomain.example.com' - ] + 'subdomain.example.com', + ], }, { key: 'protocol', @@ -18,8 +18,8 @@ export default [ `, example_values: [ 'http', - 'https' - ] + 'https', + ], }, { key: 'static_hosting_domain', @@ -30,7 +30,7 @@ export default [ you could set this to something like \`site.192.168.555.12.nip.io\`, replacing \`192.168.555.12\` with a valid IP address belonging to the server. - ` + `, }, { key: 'allow_all_host_values', @@ -44,7 +44,7 @@ export default [ description: ` If true, Puter will allow requests with host headers that end in nip.io. This is useful for development, LAN, and VPN configurations. - ` + `, }, { key: 'http_port', @@ -57,21 +57,21 @@ export default [ description: ` If true, any /username/Public directory will be available to all users, including anonymous users. - ` + `, }, { key: 'disable_temp_users', description: ` If true, new users will see the login/signup page instead of being automatically logged in as a temporary user. - ` + `, }, { key: 'disable_user_signup', description: ` If true, the signup page will be disabled and the backend will not accept new user registrations. - ` + `, }, { key: 'disable_fallback_mechanisms', @@ -79,6 +79,6 @@ export default [ A general setting to prevent any fallback behavior that might "hide" errors. It is recommended to set this to true when debugging, testing, or developing new features. - ` - } -] \ No newline at end of file + `, + }, +]; \ No newline at end of file diff --git a/doc/self-hosters/gen.js b/doc/self-hosters/gen.js index a091c5380a..fa0970254c 100644 --- a/doc/self-hosters/gen.js +++ b/doc/self-hosters/gen.js @@ -4,17 +4,17 @@ import configVals from './config-vals.json.js'; const mdlib = {}; mdlib.h = (out, n, str) => { out(`${'#'.repeat(n)} ${str}\n\n`); -} +}; const N_START = 3; const out = str => process.stdout.write(str); for ( const configVal of configVals ) { mdlib.h(out, N_START, `\`${configVal.key}\``); - out(dedent(configVal.description) + '\n\n'); - + out(`${dedent(configVal.description) }\n\n`); + if ( configVal.example_values ) { - mdlib.h(out, N_START + 1, `Examples`); + mdlib.h(out, N_START + 1, 'Examples'); for ( const example of configVal.example_values ) { out(`- \`"${configVal.key}": ${JSON.stringify(example)}\`\n`); } diff --git a/eslint.config.js b/eslint.config.js index 490ce07ce1..338d2cdbc8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,15 +10,14 @@ import spaceUnaryOpsWithException from './eslint/space-unary-ops-with-exception. const rules = { 'no-unused-vars': ['error', { - 'vars': 'all', - 'args': 'after-used', - 'caughtErrors': 'all', - 'ignoreRestSiblings': false, - 'ignoreUsingDeclarations': false, - 'reportUsedIgnorePattern': false, - 'argsIgnorePattern': '^_', - 'caughtErrorsIgnorePattern': '^_', - 'destructuredArrayIgnorePattern': '^_', + vars: 'all', + args: 'after-used', + caughtErrors: 'none', + ignoreRestSiblings: false, + ignoreUsingDeclarations: false, + reportUsedIgnorePattern: false, + argsIgnorePattern: '^_', + destructuredArrayIgnorePattern: '^_', }], curly: ['error', 'multi-line'], @@ -60,7 +59,7 @@ export default defineConfig([ // TypeScript support block { files: ['**/*.ts'], - ignores: ['tests/**/*.ts', 'extensions'], + ignores: ['tests/**/*.ts', 'extensions/**/*.ts'], languageOptions: { parser: tseslintParser, parserOptions: { @@ -75,7 +74,7 @@ export default defineConfig([ rules: { // Recommended rules for TypeScript '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }], '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], }, @@ -88,7 +87,7 @@ export default defineConfig([ parserOptions: { ecmaVersion: 'latest', sourceType: 'module', - project: 'extensions/tsconfig.json', + project: './extensions/tsconfig.json', }, }, plugins: { @@ -97,16 +96,18 @@ export default defineConfig([ rules: { // Recommended rules for TypeScript '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }], '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], - }, - }, + } }, // TypeScript support for tests { files: ['tests/**/*.ts'], + ignores: ['tests/playwright/tests/**/*.ts'], + languageOptions: { parser: tseslintParser, + globals: { ...globals.jest, ...globals.node }, parserOptions: { ecmaVersion: 'latest', sourceType: 'module', @@ -119,11 +120,10 @@ export default defineConfig([ rules: { // Recommended rules for TypeScript '@typescript-eslint/no-explicit-any': 'warn', - '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', caughtErrors: 'none' }], '@typescript-eslint/ban-ts-comment': 'warn', '@typescript-eslint/consistent-type-definitions': ['error', 'interface'], - }, - }, + } }, { plugins: { js, @@ -136,7 +136,14 @@ export default defineConfig([ }, }, { - files: ['src/backend/**/*.{js,mjs,cjs,ts}'], + files: [ + 'src/backend/**/*.{js,mjs,cjs,ts}', + 'src/backend-core-0/**/*.{js,mjs,cjs,ts}', + 'src/putility/**/*.{js,mjs,cjs,ts}', + ], + ignores: [ + '**/*.test.js', + ], languageOptions: { globals: globals.node }, rules, extends: ['js/recommended'], @@ -145,6 +152,17 @@ export default defineConfig([ '@stylistic': stylistic, }, }, + { + files: [ + '**/*.test.js', + ], + languageOptions: { globals: { ...globals.jest, ...globals.node } }, + rules, + plugins: { + js, + '@stylistic': stylistic, + }, + }, { files: ['extensions/**/*.{js,mjs,cjs,ts}'], languageOptions: { @@ -163,28 +181,29 @@ export default defineConfig([ }, }, { - files: ['**/*.{js,mjs,cjs,ts}'], + files: ['**/*.{js,mjs,cjs,ts}', 'src/gui/src/**/*.js'], ignores: [ 'src/backend/**/*.{js,mjs,cjs,ts}', 'extensions/**/*.{js,mjs,cjs,ts}', + 'src/backend-core-0/**/*.{js,mjs,cjs,ts}', + 'submodules/**', + 'tests/**', + 'tools/**', + '**/*.min.js', + '**/*.min.cjs', + '**/*.min.mjs', + '**/socket.io.js', + '**/dist/*.js', + 'src/phoenix/test/**', + 'src/gui/src/lib/**', + 'src/gui/dist/**', ], languageOptions: { globals: { ...globals.browser, ...globals.jquery, i18n: 'readonly', - }, - }, - rules, - }, - { - files: ['**/*.{js,mjs,cjs,ts}'], - ignores: ['src/backend/**/*.{js,mjs,cjs,ts}'], - languageOptions: { - globals: { - ...globals.browser, - ...globals.jquery, - i18n: 'readonly', + puter: 'readonly', }, }, rules, @@ -192,7 +211,6 @@ export default defineConfig([ plugins: { js, '@stylistic': stylistic, - }, }, ]); diff --git a/eslint/bang-space-if.js b/eslint/bang-space-if.js index 0f62166bd9..7b6c0b8dd5 100644 --- a/eslint/bang-space-if.js +++ b/eslint/bang-space-if.js @@ -29,7 +29,7 @@ export default { return { IfStatement (ifNode) { const testRaw = ifNode.test; - if ( !testRaw ) return; + if ( ! testRaw ) return; const test = unwrapParens(testRaw); if ( !test || test.type !== 'UnaryExpression' || test.operator !== '!' ) { diff --git a/eslint/control-structure-spacing.js b/eslint/control-structure-spacing.js index 7a25796af1..4fc66b2610 100644 --- a/eslint/control-structure-spacing.js +++ b/eslint/control-structure-spacing.js @@ -15,10 +15,10 @@ export default { }, }, - create(context) { + create (context) { const sourceCode = context.getSourceCode(); - function checkControlStructureSpacing(node) { + function checkControlStructureSpacing (node) { // For control structures, we need to find the parentheses around the condition/test let conditionNode; @@ -33,7 +33,7 @@ export default { conditionNode = node.param; } - if ( !conditionNode ) return; + if ( ! conditionNode ) return; // Find the opening paren - it should be right before the condition starts const openParen = sourceCode.getTokenBefore(conditionNode, token => token.value === '('); @@ -62,7 +62,7 @@ export default { node, loc: openParen.loc, messageId: 'missingSpaceAfterOpen', - fix(fixer) { + fix (fixer) { return fixer.insertTextAfter(openParen, ' '); }, }); @@ -73,25 +73,25 @@ export default { node, loc: closeParen.loc, messageId: 'missingSpaceBeforeClose', - fix(fixer) { + fix (fixer) { return fixer.insertTextBefore(closeParen, ' '); }, }); } } - function checkForLoopSpacing(node) { + function checkForLoopSpacing (node) { // For loops are special - we need to find the opening paren after the 'for' keyword // and the closing paren before the body const forKeyword = sourceCode.getFirstToken(node); if ( !forKeyword || forKeyword.value !== 'for' ) return; const openParen = sourceCode.getTokenAfter(forKeyword, token => token.value === '('); - if ( !openParen ) return; + if ( ! openParen ) return; // The closing paren should be right before the body const closeParen = sourceCode.getTokenBefore(node.body, token => token.value === ')'); - if ( !closeParen ) return; + if ( ! closeParen ) return; const afterOpen = sourceCode.getTokenAfter(openParen); const beforeClose = sourceCode.getTokenBefore(closeParen); @@ -101,7 +101,7 @@ export default { node, loc: openParen.loc, messageId: 'missingSpaceAfterOpen', - fix(fixer) { + fix (fixer) { return fixer.insertTextAfter(openParen, ' '); }, }); @@ -112,14 +112,14 @@ export default { node, loc: closeParen.loc, messageId: 'missingSpaceBeforeClose', - fix(fixer) { + fix (fixer) { return fixer.insertTextBefore(closeParen, ' '); }, }); } } - function checkFunctionCallSpacing(node) { + function checkFunctionCallSpacing (node) { // Find the opening parenthesis for this function call const openParen = sourceCode.getFirstToken(node, token => token.value === '('); const closeParen = sourceCode.getLastToken(node, token => token.value === ')'); @@ -137,7 +137,7 @@ export default { node, loc: openParen.loc, messageId: 'unexpectedSpaceAfterOpen', - fix(fixer) { + fix (fixer) { return fixer.removeRange([openParen.range[1], afterOpen.range[0]]); }, }); @@ -151,7 +151,7 @@ export default { node, loc: closeParen.loc, messageId: 'unexpectedSpaceBeforeClose', - fix(fixer) { + fix (fixer) { return fixer.removeRange([beforeClose.range[1], closeParen.range[0]]); }, }); @@ -161,40 +161,40 @@ export default { return { // Control structures that should have spacing - IfStatement(node) { + IfStatement (node) { checkControlStructureSpacing(node); }, - WhileStatement(node) { + WhileStatement (node) { checkControlStructureSpacing(node); }, - DoWhileStatement(node) { + DoWhileStatement (node) { checkControlStructureSpacing(node); }, - SwitchStatement(node) { + SwitchStatement (node) { checkControlStructureSpacing(node); }, - CatchClause(node) { + CatchClause (node) { if ( node.param ) { checkControlStructureSpacing(node); } }, // For loops need special handling - ForStatement(node) { + ForStatement (node) { checkForLoopSpacing(node); }, - ForInStatement(node) { + ForInStatement (node) { checkForLoopSpacing(node); }, - ForOfStatement(node) { + ForOfStatement (node) { checkForLoopSpacing(node); }, // Function calls that should NOT have spacing - CallExpression(node) { + CallExpression (node) { checkFunctionCallSpacing(node); }, - NewExpression(node) { + NewExpression (node) { if ( node.arguments.length > 0 || sourceCode.getLastToken(node).value === ')' ) { checkFunctionCallSpacing(node); } diff --git a/extensions/exports_something.js b/extensions/exports_something.js index 78ac7fb49a..a93fd0f954 100644 --- a/extensions/exports_something.js +++ b/extensions/exports_something.js @@ -1,7 +1,7 @@ //@puter priority -1 console.log('exporting something...'); extension.exports = { - testval: 5 + testval: 5, }; extension.on('init', () => { diff --git a/extensions/metering/routes/usage.js b/extensions/metering/routes/usage.js index 32d731af27..12b6f50f20 100644 --- a/extensions/metering/routes/usage.js +++ b/extensions/metering/routes/usage.js @@ -5,7 +5,7 @@ extension.get('/metering/usage', { subdomain: 'api' }, async (req, res) => { const meteringService = meteringServiceWrapper.meteringService; const actor = req.actor; - if ( !actor ) { + if ( ! actor ) { throw Error('actor not found in context'); } const actorUsagePromise = meteringService.getActorCurrentMonthUsageDetails(actor); @@ -20,11 +20,11 @@ extension.get('/metering/usage/:appId', { subdomain: 'api' }, async (req, res) = const meteringService = meteringServiceWrapper.meteringService; const actor = req.actor; - if ( !actor ) { + if ( ! actor ) { throw Error('actor not found in context'); } const appId = req.params.appId; - if ( !appId ) { + if ( ! appId ) { res.status(400).json({ error: 'appId parameter is required' }); return; } @@ -37,13 +37,13 @@ extension.get('/metering/usage/:appId', { subdomain: 'api' }, async (req, res) = extension.get('/metering/globalUsage', { subdomain: 'api' }, async (req, res) => { const meteringService = meteringServiceWrapper.meteringService; const actor = req.actor; - if ( !actor ) { + if ( ! actor ) { throw Error('actor not found in context'); } // check if actor is allowed to view global usage const allowedUsers = extension.config.allowedGlobalUsageUsers || []; - if ( !allowedUsers.includes(actor.type?.user.uuid) ) { + if ( ! allowedUsers.includes(actor.type?.user.uuid) ) { res.status(403).json({ error: 'You are not authorized to view global usage' }); return; } diff --git a/mods/mods_available/example-singlefile.js b/mods/mods_available/example-singlefile.js index f1aab3fd56..3cac2740a2 100644 --- a/mods/mods_available/example-singlefile.js +++ b/mods/mods_available/example-singlefile.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -23,4 +23,4 @@ extension.get('/example-onefile-get', (req, res) => { extension.on('install', ({ services }) => { // console.log('install was called'); -}) +}); diff --git a/mods/mods_available/example/main.js b/mods/mods_available/example/main.js index b5f84ef862..6ca4b6940b 100644 --- a/mods/mods_available/example/main.js +++ b/mods/mods_available/example/main.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -23,4 +23,4 @@ extension.get('/example-mod-get', (req, res) => { extension.on('install', ({ services }) => { // console.log('install was called'); -}) +}); diff --git a/mods/mods_available/kdmod/CustomPuterService.js b/mods/mods_available/kdmod/CustomPuterService.js index 24b4e72309..ec5b730d81 100644 --- a/mods/mods_available/kdmod/CustomPuterService.js +++ b/mods/mods_available/kdmod/CustomPuterService.js @@ -22,7 +22,7 @@ class CustomPuterService extends use.Service { async _init () { const svc_commands = this.services.get('commands'); this._register_commands(svc_commands); - + const svc_puterHomepage = this.services.get('puter-homepage'); svc_puterHomepage.register_script('/custom-gui/main.js'); } @@ -32,29 +32,28 @@ class CustomPuterService extends use.Service { const path_ = require('path'); app.use('/custom-gui', - express.static(path.join(__dirname, 'gui'))); + express.static(path.join(__dirname, 'gui'))); } async ['__on_boot.consolidation'] () { const then = Date.now(); this.tod_widget = () => { - const s = 5 - Math.floor( - (Date.now() - then) / 1000); + const s = 5 - Math.floor((Date.now() - then) / 1000); const lines = [ - "\x1B[36;1mKDMOD ENABLED\x1B[0m" + - ` (👁️ ${s}s)` + '\x1B[36;1mKDMOD ENABLED\x1B[0m' + + ` (👁️ ${s}s)`, ]; // It would be super cool to be able to use this here // surrounding_box('33;1', lines); return lines; - } + }; const svc_devConsole = this.services.get('dev-console', { optional: true }); if ( ! svc_devConsole ) return; svc_devConsole.add_widget(this.tod_widget); - + setTimeout(() => { svc_devConsole.remove_widget(this.tod_widget); - }, 5000) + }, 5000); } _register_commands (commands) { @@ -69,8 +68,8 @@ class CustomPuterService extends use.Service { const lines = this.tod_widget(); for ( const line of lines ) log.log(line); this.tod_widget = null; - } - } + }, + }, ]); } } diff --git a/mods/mods_available/kdmod/ShareTestService.js b/mods/mods_available/kdmod/ShareTestService.js index 653db53cc8..8eb434245a 100644 --- a/mods/mods_available/kdmod/ShareTestService.js +++ b/mods/mods_available/kdmod/ShareTestService.js @@ -41,7 +41,7 @@ class ShareTestService extends use.Service { uuidv4: require('uuid').v4, }; - async _init() { + async _init () { const svc_commands = this.services.get('commands'); this._register_commands(svc_commands); @@ -51,7 +51,7 @@ class ShareTestService extends use.Service { this.db = svc_db.get(svc_db.DB_WRITE, 'share-test'); } - _register_commands(commands) { + _register_commands (commands) { commands.registerCommands('share-test', [ { id: 'start', @@ -74,7 +74,7 @@ class ShareTestService extends use.Service { ]); } - async runit() { + async runit () { await this.teardown_(); await this.setup_(); @@ -94,13 +94,13 @@ class ShareTestService extends use.Service { return results; } - async setup_() { + async setup_ () { await this.create_test_user_('testuser_eric'); await this.create_test_user_('testuser_stan'); await this.create_test_user_('testuser_kyle'); await this.create_test_user_('testuser_kenny'); } - async run_scenario_(scenario) { + async run_scenario_ (scenario) { let error; // Run sequence for ( const step of scenario.sequence ) { @@ -119,14 +119,14 @@ class ShareTestService extends use.Service { } return error; } - async teardown_() { + async teardown_ () { await this.delete_test_user_('testuser_eric'); await this.delete_test_user_('testuser_stan'); await this.delete_test_user_('testuser_kyle'); await this.delete_test_user_('testuser_kenny'); } - async create_test_user_(username) { + async create_test_user_ (username) { await this.db.write(` INSERT INTO user (uuid, username, email, free_storage, password) VALUES (?, ?, ?, ?, ?) @@ -145,14 +145,14 @@ class ShareTestService extends use.Service { return user; } - async delete_test_user_(username) { + async delete_test_user_ (username) { const user = await get_user({ username }); if ( ! user ) return; await deleteUser(user.id); } // API for scenarios - async ['__scenario:create-example-file']( + async ['__scenario:create-example-file'] ( { actor, user }, { name, contents }, ) { @@ -178,7 +178,7 @@ class ShareTestService extends use.Service { file, }); } - async ['__scenario:assert-no-access']( + async ['__scenario:assert-no-access'] ( { actor, user }, { path }, ) { @@ -190,21 +190,21 @@ class ShareTestService extends use.Service { fsNode: node, actor, }); - } catch(e) { + } catch (e) { expected_e = e; } if ( ! expected_e ) { return { message: 'expected error, got none' }; } } - async ['__scenario:grant']( + async ['__scenario:grant'] ( { actor, user }, { to, permission }, ) { const svc_permission = this.services.get('permission'); await svc_permission.grant_user_user_permission(actor, to, permission, {}, {}); } - async ['__scenario:assert-access']( + async ['__scenario:assert-access'] ( { actor, user }, { path, level }, ) { diff --git a/mods/mods_available/kdmod/data/sharetest_scenarios.js b/mods/mods_available/kdmod/data/sharetest_scenarios.js index d6abe836ea..02c3d61ac7 100644 --- a/mods/mods_available/kdmod/data/sharetest_scenarios.js +++ b/mods/mods_available/kdmod/data/sharetest_scenarios.js @@ -26,17 +26,17 @@ module.exports = [ with: { name: 'example.txt', contents: 'secret file', - } + }, }, { title: 'Eric tries to access it', call: 'assert-no-access', as: 'testuser_eric', with: { - path: '/testuser_kyle/Desktop/example.txt' - } + path: '/testuser_kyle/Desktop/example.txt', + }, }, - ] + ], }, { sequence: [ @@ -47,7 +47,7 @@ module.exports = [ with: { name: 'example.txt', contents: 'secret file', - } + }, }, { title: 'Stan grants permission to Eric', @@ -55,8 +55,8 @@ module.exports = [ as: 'testuser_stan', with: { to: 'testuser_eric', - permission: 'fs:/testuser_stan/Desktop/example.txt:read' - } + permission: 'fs:/testuser_stan/Desktop/example.txt:read', + }, }, { title: 'Eric tries to access it', @@ -64,10 +64,10 @@ module.exports = [ as: 'testuser_eric', with: { path: '/testuser_stan/Desktop/example.txt', - level: 'read' - } + level: 'read', + }, }, - ] + ], }, { sequence: [ @@ -77,8 +77,8 @@ module.exports = [ as: 'testuser_stan', with: { to: 'testuser_eric', - permission: 'fs:/testuser_kyle/Desktop/example.txt:read' - } + permission: 'fs:/testuser_kyle/Desktop/example.txt:read', + }, }, { title: 'Eric tries to access it', @@ -86,8 +86,8 @@ module.exports = [ as: 'testuser_eric', with: { path: '/testuser_kyle/Desktop/example.txt', - } + }, }, - ] + ], }, ]; diff --git a/mods/mods_available/kdmod/gui/main.js b/mods/mods_available/kdmod/gui/main.js index 22f2faa810..cdcb7f7231 100644 --- a/mods/mods_available/kdmod/gui/main.js +++ b/mods/mods_available/kdmod/gui/main.js @@ -22,15 +22,15 @@ const request_examples = [ fetch: async (args) => { return await fetch(`${window.api_origin}/drivers/call`, { headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${puter.authToken}`, + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${puter.authToken}`, }, body: JSON.stringify({ interface: 'puter-apps', method: 'read', args, }), - method: "POST", + method: 'POST', }); }, out: async (resp) => { @@ -48,15 +48,15 @@ const request_examples = [ fetch: async () => { return await fetch(`${window.api_origin}/drivers/call`, { headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${puter.authToken}`, + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${puter.authToken}`, }, body: JSON.stringify({ interface: 'puter-apps', method: 'select', args: { predicate: [] }, }), - method: "POST", + method: 'POST', }); }, out: async (resp) => { @@ -73,15 +73,15 @@ const request_examples = [ name: 'grant permission from a user to a user', fetch: async (user, perm) => { return await fetch(`${window.api_origin}/auth/grant-user-user`, { - "headers": { - "Content-Type": "application/json", - "Authorization": `Bearer ${puter.authToken}`, - }, - "body": JSON.stringify({ - target_username: user, - permission: perm, - }), - "method": "POST", + 'headers': { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${puter.authToken}`, + }, + 'body': JSON.stringify({ + target_username: user, + permission: perm, + }), + 'method': 'POST', }); }, out: async (resp) => { @@ -98,7 +98,7 @@ const request_examples = [ fetch: async (path, str) => { const endpoint = `${window.api_origin}/write`; const token = puter.authToken; - + const blob = new Blob([str], { type: 'text/plain' }); const formData = new FormData(); formData.append('create_missing_ancestors', true); @@ -106,15 +106,15 @@ const request_examples = [ formData.append('size', 8); formData.append('overwrite', true); formData.append('file', blob, 'something.txt'); - + const response = await fetch(endpoint, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, - body: formData + body: formData, }); return await response.json(); }, - } + }, ]; globalThis.reqex = request_examples; diff --git a/mods/mods_available/kdmod/module.js b/mods/mods_available/kdmod/module.js index 97e0f41e50..c2d38a5f2d 100644 --- a/mods/mods_available/kdmod/module.js +++ b/mods/mods_available/kdmod/module.js @@ -19,7 +19,7 @@ extension.on('install', ({ services }) => { const { CustomPuterService } = require('./CustomPuterService.js'); services.registerService('__custom-puter', CustomPuterService); - + const { ShareTestService } = require('./ShareTestService.js'); services.registerService('__share-test', ShareTestService); }); diff --git a/package.json b/package.json index d559ca784c..c200f4e0d4 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "devDependencies": { "@eslint/js": "^9.35.0", + "@playwright/test": "^1.56.1", "@stylistic/eslint-plugin": "^5.3.1", "@types/uuid": "^10.0.0", "@typescript-eslint/eslint-plugin": "^8.46.1", @@ -35,7 +36,8 @@ "vite-plugin-static-copy": "^3.1.3", "vitest": "^3.2.4", "webpack": "^5.88.2", - "webpack-cli": "^5.1.1" + "webpack-cli": "^5.1.1", + "yaml": "^2.8.1" }, "scripts": { "test": "npx mocha src/phoenix/test && npx vitest run src/backend && node src/backend/tools/test", @@ -89,4 +91,4 @@ "engines": { "node": ">=20.19.5" } -} \ No newline at end of file +} diff --git a/src/backend-core-0/src/pdim/validation.js b/src/backend-core-0/src/pdim/validation.js index c3e6e08682..d013494956 100644 --- a/src/backend-core-0/src/pdim/validation.js +++ b/src/backend-core-0/src/pdim/validation.js @@ -1,25 +1,25 @@ export const is_valid_uuid = ( uuid ) => { - let s = "" + uuid; + let s = `${ uuid}`; s = s.match(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i); - return !! s; -} + return !!s; +}; export const is_valid_uuid4 = ( uuid ) => { return is_valid_uuid(uuid); -} +}; export const is_specifically_uuidv4 = ( uuid ) => { - let s = "" + uuid; + let s = `${ uuid}`; s = s.match(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i); - if (!s) { - return false; + if ( ! s ) { + return false; } return true; -} +}; export const is_valid_url = ( url ) => { - let s = "" + url; + let s = `${ url}`; try { new URL(s); @@ -27,7 +27,7 @@ export const is_valid_url = ( url ) => { } catch (e) { return false; } -} +}; const path_excludes = () => /[\x00-\x1F]/g; @@ -40,8 +40,8 @@ const safety_excludes = [ /[\u2066-\u2069]/, // RTL and LTR isolate /[\u2028-\u2029]/, // line and paragraph separator /[\uFF01-\uFF5E]/, // fullwidth ASCII - /[\u2060]/, // word joiner - /[\uFEFF]/, // zero width no-break space + /[\u2060]/, // word joiner + /[\uFEFF]/, // zero width no-break space /[\uFFFE-\uFFFF]/, // non-characters ]; @@ -56,8 +56,10 @@ export const is_valid_path = (path, { if ( exclude.test(path) ) return false; } - if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) { - return false; + if ( ! allow_path_fragment ) { + if ( path[0] !== '/' && path[0] !== '.' ) { + return false; + } } if ( no_relative_components ) { @@ -70,4 +72,4 @@ export const is_valid_path = (path, { } return true; -} +}; diff --git a/src/backend-core-0/webpack.config.js b/src/backend-core-0/webpack.config.js index 7f2ce50457..87489833b5 100644 --- a/src/backend-core-0/webpack.config.js +++ b/src/backend-core-0/webpack.config.js @@ -51,4 +51,3 @@ const cjsConfig = { }; export default [esmConfig, cjsConfig]; - diff --git a/src/backend/exports.js b/src/backend/exports.js index ef002e1744..adabae18a1 100644 --- a/src/backend/exports.js +++ b/src/backend/exports.js @@ -16,33 +16,33 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const CoreModule = require("./src/CoreModule.js"); -const { Kernel } = require("./src/Kernel.js"); -const DatabaseModule = require("./src/DatabaseModule.js"); -const LocalDiskStorageModule = require("./src/LocalDiskStorageModule.js"); -const MemoryStorageModule = require("./src/MemoryStorageModule.js"); -const SelfHostedModule = require("./src/modules/selfhosted/SelfHostedModule.js"); -const { testlaunch } = require("./src/index.js"); -const BaseService = require("./src/services/BaseService.js"); -const { Context } = require("./src/util/context.js"); -const { TestDriversModule } = require("./src/modules/test-drivers/TestDriversModule.js"); -const { PuterAIModule } = require("./src/modules/puterai/PuterAIModule.js"); -const { BroadcastModule } = require("./src/modules/broadcast/BroadcastModule.js"); -const { WebModule } = require("./src/modules/web/WebModule.js"); -const { Core2Module } = require("./src/modules/core/Core2Module.js"); -const { TemplateModule } = require("./src/modules/template/TemplateModule.js"); -const { PuterFSModule } = require("./src/modules/puterfs/PuterFSModule.js"); -const { PerfMonModule } = require("./src/modules/perfmon/PerfMonModule.js"); -const { AppsModule } = require("./src/modules/apps/AppsModule.js"); -const { DevelopmentModule } = require("./src/modules/development/DevelopmentModule.js"); -const { HostOSModule } = require("./src/modules/hostos/HostOSModule.js"); -const { InternetModule } = require("./src/modules/internet/InternetModule.js"); -const { CaptchaModule } = require("./src/modules/captcha/CaptchaModule.js"); -const { EntityStoreModule } = require("./src/modules/entitystore/EntityStoreModule.js"); -const { KVStoreModule } = require("./src/modules/kvstore/KVStoreModule.js"); -const { DomainModule } = require("./src/modules/domain/DomainModule.js"); -const { DNSModule } = require("./src/modules/dns/DNSModule.js"); -const { TestConfigModule } = require("./src/modules/test-config/TestConfigModule.js"); +const CoreModule = require('./src/CoreModule.js'); +const { Kernel } = require('./src/Kernel.js'); +const DatabaseModule = require('./src/DatabaseModule.js'); +const LocalDiskStorageModule = require('./src/LocalDiskStorageModule.js'); +const MemoryStorageModule = require('./src/MemoryStorageModule.js'); +const SelfHostedModule = require('./src/modules/selfhosted/SelfHostedModule.js'); +const { testlaunch } = require('./src/index.js'); +const BaseService = require('./src/services/BaseService.js'); +const { Context } = require('./src/util/context.js'); +const { TestDriversModule } = require('./src/modules/test-drivers/TestDriversModule.js'); +const { PuterAIModule } = require('./src/modules/puterai/PuterAIModule.js'); +const { BroadcastModule } = require('./src/modules/broadcast/BroadcastModule.js'); +const { WebModule } = require('./src/modules/web/WebModule.js'); +const { Core2Module } = require('./src/modules/core/Core2Module.js'); +const { TemplateModule } = require('./src/modules/template/TemplateModule.js'); +const { PuterFSModule } = require('./src/modules/puterfs/PuterFSModule.js'); +const { PerfMonModule } = require('./src/modules/perfmon/PerfMonModule.js'); +const { AppsModule } = require('./src/modules/apps/AppsModule.js'); +const { DevelopmentModule } = require('./src/modules/development/DevelopmentModule.js'); +const { HostOSModule } = require('./src/modules/hostos/HostOSModule.js'); +const { InternetModule } = require('./src/modules/internet/InternetModule.js'); +const { CaptchaModule } = require('./src/modules/captcha/CaptchaModule.js'); +const { EntityStoreModule } = require('./src/modules/entitystore/EntityStoreModule.js'); +const { KVStoreModule } = require('./src/modules/kvstore/KVStoreModule.js'); +const { DomainModule } = require('./src/modules/domain/DomainModule.js'); +const { DNSModule } = require('./src/modules/dns/DNSModule.js'); +const { TestConfigModule } = require('./src/modules/test-config/TestConfigModule.js'); module.exports = { helloworld: () => { @@ -56,7 +56,7 @@ module.exports = { Context, Kernel, - + EssentialModules: [ Core2Module, PuterFSModule, @@ -69,7 +69,7 @@ module.exports = { EntityStoreModule, KVStoreModule, ], - + // Pre-built modules CoreModule, WebModule, @@ -86,7 +86,7 @@ module.exports = { KVStoreModule, DNSModule, DomainModule, - + // Development modules PerfMonModule, DevelopmentModule, diff --git a/src/backend/src/DatabaseModule.js b/src/backend/src/DatabaseModule.js index 771d8e7ce7..a201874628 100644 --- a/src/backend/src/DatabaseModule.js +++ b/src/backend/src/DatabaseModule.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class DatabaseModule extends AdvancedBase { async install (context) { @@ -28,8 +28,8 @@ class DatabaseModule extends AdvancedBase { strategy_key: 'engine', strategies: { sqlite: [SqliteDatabaseAccessService], - } - }) + }, + }); } } diff --git a/src/backend/src/Extension.js b/src/backend/src/Extension.js index 602900f2bd..4a364ab182 100644 --- a/src/backend/src/Extension.js +++ b/src/backend/src/Extension.js @@ -40,7 +40,7 @@ class Extension extends AdvancedBase { }), ]; - randomBrightColor() { + randomBrightColor () { // Bright colors in ANSI (foreground codes 90–97) const brightColors = [ // 91, // Bright Red @@ -54,7 +54,7 @@ class Extension extends AdvancedBase { return brightColors[Math.floor(Math.random() * brightColors.length)]; } - constructor(...a) { + constructor (...a) { super(...a); this.service = null; this.log = null; @@ -97,18 +97,18 @@ class Extension extends AdvancedBase { }; } - example() { + example () { console.log('Example method called by an extension.'); } // === [START] RuntimeModule aliases === - set exports(value) { + set exports (value) { this.runtime.exports = value; } - get exports() { + get exports () { return this.runtime.exports; } - import(name) { + import (name) { return this.runtime.import(name); } // === [END] RuntimeModule aliases === @@ -116,7 +116,7 @@ class Extension extends AdvancedBase { /** * This will get a database instance from the default service. */ - get db() { + get db () { const db = this.service.values.get('db'); if ( ! db ) { throw new Error('extension tried to access database before it was ' + @@ -125,7 +125,7 @@ class Extension extends AdvancedBase { return db; } - get services() { + get services () { const services = this.service.values.get('services'); if ( ! services ) { throw new Error('extension tried to access "services" before it was ' + @@ -134,7 +134,7 @@ class Extension extends AdvancedBase { return services; } - get log_context() { + get log_context () { const log_context = this.service.values.get('log_context'); if ( ! log_context ) { throw new Error('extension tried to access "log_context" before it was ' + @@ -142,7 +142,7 @@ class Extension extends AdvancedBase { } return log_context; } - + get errors () { return memoized_errors ?? (() => { return this.services.get('error-service').create(this.log_context); @@ -155,7 +155,7 @@ class Extension extends AdvancedBase { * @param {string} [key] Key of data being registered * @param {any} data The data to be registered */ - register(typeKey, keyOrData, data) { + register (typeKey, keyOrData, data) { if ( ! this.registry_[typeKey] ) { this.registry_[typeKey] = { named: {}, @@ -191,7 +191,7 @@ class Extension extends AdvancedBase { * @param {string} [key] Key of data being registered * @param {any} data The data to be registered */ - reg(...a) { + reg (...a) { this.register(...a); } @@ -201,7 +201,7 @@ class Extension extends AdvancedBase { * @param {*} handler - function to handle the endpoint * @param {*} options - options like noauth (bool) and mw (array) */ - get(path, handler, options) { + get (path, handler, options) { // this extension will have a default service this.ensure_service_(); @@ -223,7 +223,7 @@ class Extension extends AdvancedBase { * @param {*} handler - function to handle the endpoint * @param {*} options - options like noauth (bool) and mw (array) */ - post(path, handler, options) { + post (path, handler, options) { // this extension will have a default service this.ensure_service_(); @@ -245,7 +245,7 @@ class Extension extends AdvancedBase { * @param {*} handler - function to handle the endpoint * @param {*} options - options like noauth (bool) and mw (array) */ - put(path, handler, options) { + put (path, handler, options) { // this extension will have a default service this.ensure_service_(); @@ -267,7 +267,7 @@ class Extension extends AdvancedBase { * @param {*} options - options like noauth (bool) and mw (array) */ - delete(path, handler, options) { + delete (path, handler, options) { // this extension will have a default service this.ensure_service_(); @@ -283,7 +283,7 @@ class Extension extends AdvancedBase { }); } - use(...args) { + use (...args) { this.ensure_service_(); this.service.expressThings_.push({ type: 'router', @@ -291,12 +291,12 @@ class Extension extends AdvancedBase { }); } - get preinit() { - return (function(callback) { + get preinit () { + return (function (callback) { this.on('preinit', callback); }).bind(this); } - set preinit(callback) { + set preinit (callback) { if ( this.only_one_preinit_fn === null ) { this.on('preinit', (...a) => { this.only_one_preinit_fn(...a); @@ -309,12 +309,12 @@ class Extension extends AdvancedBase { this.only_one_preinit_fn = callback; } - get init() { - return (function(callback) { + get init () { + return (function (callback) { this.on('init', callback); }).bind(this); } - set init(callback) { + set init (callback) { if ( this.only_one_init_fn === null ) { this.on('init', (...a) => { this.only_one_init_fn(...a); @@ -327,7 +327,7 @@ class Extension extends AdvancedBase { this.only_one_init_fn = callback; } - get console() { + get console () { const extensionConsole = Object.create(console); const logfn = level => (...a) => { let svc_log; @@ -368,7 +368,7 @@ class Extension extends AdvancedBase { * * @returns {void} */ - ensure_service_() { + ensure_service_ () { if ( this.service ) { return; } diff --git a/src/backend/src/ExtensionModule.js b/src/backend/src/ExtensionModule.js index e292642308..b19fd63368 100644 --- a/src/backend/src/ExtensionModule.js +++ b/src/backend/src/ExtensionModule.js @@ -1,30 +1,30 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); const uuid = require('uuid'); -const { ExtensionService } = require("./ExtensionService"); +const { ExtensionService } = require('./ExtensionService'); class ExtensionModule extends AdvancedBase { async install (context) { const services = context.get('services'); - + this.extension.name = this.extension.name ?? context.name; this.extension.emit('install', { context, services }); diff --git a/src/backend/src/ExtensionService.js b/src/backend/src/ExtensionService.js index d08927158e..e0bfbffcaa 100644 --- a/src/backend/src/ExtensionService.js +++ b/src/backend/src/ExtensionService.js @@ -31,7 +31,7 @@ const { Actor } = require('./services/auth/Actor'); * future) to the default service. */ class ExtensionServiceState extends AdvancedBase { - constructor(...a) { + constructor (...a) { super(...a); this.extension = a[0].extension; @@ -41,7 +41,7 @@ class ExtensionServiceState extends AdvancedBase { // Values shared between the `extension` global and its service this.values = new Context(); } - register_route_handler_(path, handler, options = {}) { + register_route_handler_ (path, handler, options = {}) { // handler and options may be flipped if ( typeof handler === 'object' ) { [handler, options] = [options, handler]; @@ -77,10 +77,10 @@ class ExtensionServiceState extends AdvancedBase { * provide a default service for extensions. */ class ExtensionService extends BaseService { - _construct() { + _construct () { this.expressThings_ = []; } - async _init(args) { + async _init (args) { this.state = args.state; this.state.values.set('services', this.services); @@ -153,7 +153,7 @@ class ExtensionService extends BaseService { this.state.extension.emit('preinit'); } - async ['__on_boot.consolidation'](...a) { + async ['__on_boot.consolidation'] (...a) { const svc_su = this.services.get('su'); await svc_su.sudo(async () => { await this.state.extension.emit('init', {}, { @@ -161,7 +161,7 @@ class ExtensionService extends BaseService { }); }); } - async ['__on_boot.activation'](...a) { + async ['__on_boot.activation'] (...a) { const svc_su = this.services.get('su'); await svc_su.sudo(async () => { await this.state.extension.emit('activate', {}, { @@ -169,7 +169,7 @@ class ExtensionService extends BaseService { }); }); } - async ['__on_boot.ready'](...a) { + async ['__on_boot.ready'] (...a) { const svc_su = this.services.get('su'); await svc_su.sudo(async () => { await this.state.extension.emit('ready', {}, { @@ -178,7 +178,7 @@ class ExtensionService extends BaseService { }); } - ['__on_install.routes'](_, { app }) { + ['__on_install.routes'] (_, { app }) { if ( ! this.state ) debugger; for ( const thing of this.state.expressThings_ ) { if ( thing.type === 'endpoint' ) { diff --git a/src/backend/src/Kernel.js b/src/backend/src/Kernel.js index daaa41784b..55a839cb76 100644 --- a/src/backend/src/Kernel.js +++ b/src/backend/src/Kernel.js @@ -16,25 +16,25 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase, libs } = require("@heyputer/putility"); +const { AdvancedBase, libs } = require('@heyputer/putility'); const { Context } = require('./util/context'); -const BaseService = require("./services/BaseService"); +const BaseService = require('./services/BaseService'); const useapi = require('useapi'); -const yargs = require('yargs/yargs') +const yargs = require('yargs/yargs'); const { hideBin } = require('yargs/helpers'); -const { Extension } = require("./Extension"); -const { ExtensionModule } = require("./ExtensionModule"); -const { spawn } = require("node:child_process"); +const { Extension } = require('./Extension'); +const { ExtensionModule } = require('./ExtensionModule'); +const { spawn } = require('node:child_process'); const fs = require('fs'); const path_ = require('path'); -const { prependToJSFiles } = require("./kernel/modutil"); +const { prependToJSFiles } = require('./kernel/modutil'); const uuid = require('uuid'); -const readline = require("node:readline/promises"); -const { RuntimeModuleRegistry } = require("./extension/RuntimeModuleRegistry"); -const { RuntimeModule } = require("./extension/RuntimeModule"); -const deep_proto_merge = require("./config/deep_proto_merge"); +const readline = require('node:readline/promises'); +const { RuntimeModuleRegistry } = require('./extension/RuntimeModuleRegistry'); +const { RuntimeModule } = require('./extension/RuntimeModule'); +const deep_proto_merge = require('./config/deep_proto_merge'); const { quot } = libs.string; @@ -54,7 +54,7 @@ class Kernel extends AdvancedBase { this.extensionExports = {}; this.extensionInfo = {}; this.registry = {}; - + this.runtimeModuleRegistry = new RuntimeModuleRegistry(); } @@ -90,17 +90,18 @@ class Kernel extends AdvancedBase { } boot () { - const args = yargs(hideBin(process.argv)).argv + const args = yargs(hideBin(process.argv)).argv; this._runtime_init({ args }); const config = require('./config'); globalThis.ll = o => o; - globalThis.xtra_log = () => {}; + globalThis.xtra_log = () => { + }; if ( config.env === 'dev' ) { globalThis.ll = o => { - console.log('debug: ' + require('node:util').inspect(o)); + console.log(`debug: ${ require('node:util').inspect(o)}`); return o; }; globalThis.xtra_log = (...args) => { @@ -108,8 +109,8 @@ class Kernel extends AdvancedBase { const fs = require('fs'); const path = require('path'); const log_path = path.join('/tmp/xtra_log.txt'); - fs.appendFileSync(log_path, args.join(' ') + '\n'); - } + fs.appendFileSync(log_path, `${args.join(' ') }\n`); + }; } const { consoleLogManager } = require('./util/consolelog'); @@ -140,7 +141,6 @@ class Kernel extends AdvancedBase { await this._boot_services(); }); - Error.stackTraceLimit = 200; } @@ -157,7 +157,7 @@ class Kernel extends AdvancedBase { }); await module_.install(mod_context); } - + for ( const k in services.instances_ ) { const service_exports = new RuntimeModule({ name: `service:${k}` }); this.runtimeModuleRegistry.register(service_exports); @@ -188,10 +188,8 @@ class Kernel extends AdvancedBase { throw e; } - await svc_systemValidation.mark_invalid( - 'failed to initialize services', - e, - ); + await svc_systemValidation.mark_invalid('failed to initialize services', + e); } for ( const module of this.modules ) { @@ -228,24 +226,24 @@ class Kernel extends AdvancedBase { } async install_extern_mods_ () { - + // In runtime directory, we'll create a `mod_packages` directory.` if ( fs.existsSync('mod_packages') ) { fs.rmSync('mod_packages', { recursive: true, force: true }); } fs.mkdirSync('mod_packages'); - + // Initialize some globals that external mods depend on globalThis.__puter_extension_globals__ = { extensionObjectRegistry: {}, useapi: this.useapi, global_config: require('./config'), }; - + // Install the mods... - + const mod_install_root_context = Context.get(); - + const mod_directory_promises = []; const mod_installation_promises = []; @@ -253,9 +251,7 @@ class Kernel extends AdvancedBase { for ( const mods_dirpath of mod_paths ) { const p = (async () => { if ( ! fs.existsSync(mods_dirpath) ) { - this.services.logger.error( - `mod directory not found: ${quot(mods_dirpath)}; skipping...` - ); + this.services.logger.error(`mod directory not found: ${quot(mods_dirpath)}; skipping...`); // intentional delay so error is seen this.services.logger.info('boot will continue in 4 seconds'); await new Promise(rslv => setTimeout(rslv, 4000)); @@ -266,7 +262,7 @@ class Kernel extends AdvancedBase { const ignoreList = new Set([ '.git', ]); - + for ( const mod_dirname of mod_dirnames ) { if ( ignoreList.has(mod_dirname) ) continue; mod_installation_promises.push(this.install_extern_mod_({ @@ -279,31 +275,31 @@ class Kernel extends AdvancedBase { if ( process.env.SYNC_MOD_INSTALL ) await p; mod_directory_promises.push(p); } - + await Promise.all(mod_directory_promises); - + const mods_to_run = (await Promise.all(mod_installation_promises)) .filter(v => v !== undefined); mods_to_run.sort((a, b) => a.priority - b.priority); let i = 0; - while (i < mods_to_run.length) { + while ( i < mods_to_run.length ) { const currentPriority = mods_to_run[i].priority; const samePriorityMods = []; - + // Collect all mods with the same priority - while (i < mods_to_run.length && mods_to_run[i].priority === currentPriority) { + while ( i < mods_to_run.length && mods_to_run[i].priority === currentPriority ) { samePriorityMods.push(mods_to_run[i]); i++; } - + // Run all mods with the same priority concurrently await Promise.all(samePriorityMods.map(mod_entry => { return this._run_extern_mod(mod_entry); })); } } - - async install_extern_mod_({ + + async install_extern_mod_ ({ mod_install_root_context, mod_dirname, mod_path, @@ -315,19 +311,19 @@ class Kernel extends AdvancedBase { } // Mod must be a directory or javascript file - if ( ! stat.isDirectory() && !(mod_path.endsWith('.js')) ) { + if ( !stat.isDirectory() && !(mod_path.endsWith('.js')) ) { return; } - + let mod_name = path_.parse(mod_path).name; const mod_package_dir = `mod_packages/${mod_name}`; fs.mkdirSync(mod_package_dir); - + const mod_entry = { priority: 0, jsons: {}, }; - + if ( ! stat.isDirectory() ) { const rl = readline.createInterface({ input: fs.createReadStream(mod_path), @@ -340,7 +336,7 @@ class Kernel extends AdvancedBase { mod_entry.priority = Number(tokens[2]); } if ( tokens[1] === 'name' ) { - mod_name = '' + tokens[2]; + mod_name = `${ tokens[2]}`; } } mod_entry.jsons.package = await this.create_mod_package_json(mod_package_dir, { @@ -354,7 +350,7 @@ class Kernel extends AdvancedBase { this.bootLogger.warn(`Empty mod directory ${quot(mod_path)}; skipping...`); return; } - + const promises = []; // Create package.json if it doesn't exist @@ -369,7 +365,7 @@ class Kernel extends AdvancedBase { mod_entry.jsons.package = JSON.parse(str); } })()); - + const puter_json_path = path_.join(mod_path, 'puter.json'); if ( fs.existsSync(puter_json_path) ) { promises.push((async () => { @@ -391,45 +387,45 @@ class Kernel extends AdvancedBase { mod_entry.jsons.config = obj; })()); } - + // Copy mod contents to `/mod_packages` promises.push(fs.promises.cp(mod_path, mod_package_dir, { recursive: true, })); - + await Promise.all(promises); } - + mod_entry.priority = mod_entry.jsons.puter?.priority ?? mod_entry.priority; - + const extension_id = uuid.v4(); - - await prependToJSFiles(mod_package_dir, [ - `const { use, def } = globalThis.__puter_extension_globals__.useapi;`, - `const { use: puter } = globalThis.__puter_extension_globals__.useapi;`, - `const extension = globalThis.__puter_extension_globals__` + - `.extensionObjectRegistry[${JSON.stringify(extension_id)}];`, - `const console = extension.console;`, - `const runtime = extension.runtime;`, - `const config = extension.config;`, - `const registry = extension.registry;`, - `const register = registry.register;`, - `const global_config = globalThis.__puter_extension_globals__.global_config`, - ].join('\n') + '\n'); + + await prependToJSFiles(mod_package_dir, `${[ + 'const { use, def } = globalThis.__puter_extension_globals__.useapi;', + 'const { use: puter } = globalThis.__puter_extension_globals__.useapi;', + 'const extension = globalThis.__puter_extension_globals__' + + `.extensionObjectRegistry[${JSON.stringify(extension_id)}];`, + 'const console = extension.console;', + 'const runtime = extension.runtime;', + 'const config = extension.config;', + 'const registry = extension.registry;', + 'const register = registry.register;', + 'const global_config = globalThis.__puter_extension_globals__.global_config', + ].join('\n') }\n`); mod_entry.require_dir = path_.join(process.cwd(), mod_package_dir); - + await this.run_npm_install(mod_entry.require_dir); - + const mod = new ExtensionModule(); mod.extension = new Extension(); - + const runtimeModule = new RuntimeModule({ name: mod_name }); this.runtimeModuleRegistry.register(runtimeModule); mod.extension.runtime = runtimeModule; mod_entry.module = mod; - + globalThis.__puter_extension_globals__.extensionObjectRegistry[extension_id] = mod.extension; @@ -439,23 +435,23 @@ class Kernel extends AdvancedBase { external: true, mod_path, }); - + mod_entry.context = mod_context; return mod_entry; }; - - async _run_extern_mod(mod_entry) { + + async _run_extern_mod (mod_entry) { let exportObject = null; - + const { module: mod, require_dir, context, } = mod_entry; - + const packageJSON = mod_entry.jsons.package; - + Object.defineProperty(mod.extension, 'config', { get: () => { const builtin_config = mod_entry.jsons.config ?? {}; @@ -463,9 +459,9 @@ class Kernel extends AdvancedBase { return deep_proto_merge(user_config, builtin_config); }, }); - + mod.extension.name = packageJSON.name; - + const maybe_promise = (typ => typ.trim().toLowerCase())(packageJSON.type ?? '') === 'module' ? await import(path_.join(require_dir, packageJSON.main ?? 'index.js')) : require(require_dir); @@ -473,7 +469,7 @@ class Kernel extends AdvancedBase { if ( maybe_promise && maybe_promise instanceof Promise ) { exportObject = await maybe_promise; } else exportObject = maybe_promise; - + const extension_name = exportObject?.name ?? packageJSON.name; this.extensionExports[extension_name] = exportObject; this.extensionInfo[extension_name] = { @@ -483,7 +479,7 @@ class Kernel extends AdvancedBase { }; mod.extension.registry = this.registry; mod.extension.name = extension_name; - + if ( exportObject.construct ) { mod.extension.on('construct', exportObject.construct); } @@ -503,7 +499,7 @@ class Kernel extends AdvancedBase { const modapi = {}; let mod_path = options.mod_path; - if ( ! mod_path && options.module.dirname ) { + if ( !mod_path && options.module.dirname ) { mod_path = options.module.dirname(); } @@ -531,13 +527,13 @@ class Kernel extends AdvancedBase { // getter-like behavior to useapi. this.useapi.def(`${prefix}.${name}`, lib); } - } + }; } const mod_context = parent.sub({ modapi }, `mod:${options.name}`); return mod_context; } - + async create_mod_package_json (mod_path, { name, entry }) { // Expect main.js or index.js to exist const options = ['main.js', 'index.js']; @@ -556,7 +552,7 @@ class Kernel extends AdvancedBase { if ( ! entry ) { this.bootLogger.error(`Expected main.js or index.js in ${quot(mod_path)}`); if ( ! process.env.SKIP_INVALID_MODS ) { - this.bootLogger.error(`Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.`); + this.bootLogger.error('Set SKIP_INVALID_MODS=1 (environment variable) to run anyway.'); process.exit(1); } else { return; @@ -569,16 +565,16 @@ class Kernel extends AdvancedBase { main: entry ?? 'main.js', }; const data_json = JSON.stringify(data); - - this.bootLogger.debug('WRITING TO: ' + path_.join(mod_path, 'package.json')); - + + this.bootLogger.debug(`WRITING TO: ${ path_.join(mod_path, 'package.json')}`); + await fs.promises.writeFile(path_.join(mod_path, 'package.json'), data_json); return data; } async run_npm_install (path) { - const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; - const proc = spawn(npmCmd, ["install"], { cwd: path, stdio: "pipe" }); + const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm'; + const proc = spawn(npmCmd, ['install'], { cwd: path, stdio: 'pipe' }); let buffer = ''; diff --git a/src/backend/src/LocalDiskStorageModule.js b/src/backend/src/LocalDiskStorageModule.js index cc1212b7fc..922040eed0 100644 --- a/src/backend/src/LocalDiskStorageModule.js +++ b/src/backend/src/LocalDiskStorageModule.js @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class LocalDiskStorageModule extends AdvancedBase { async install (context) { const services = context.get('services'); - const LocalDiskStorageService = require("./services/LocalDiskStorageService"); + const LocalDiskStorageService = require('./services/LocalDiskStorageService'); services.registerService('local-disk-storage', LocalDiskStorageService); const HostDiskUsageService = require('./services/HostDiskUsageService'); diff --git a/src/backend/src/MemoryStorageModule.js b/src/backend/src/MemoryStorageModule.js index a8985460c4..24aa770dcc 100644 --- a/src/backend/src/MemoryStorageModule.js +++ b/src/backend/src/MemoryStorageModule.js @@ -19,7 +19,7 @@ class MemoryStorageModule { async install (context) { const services = context.get('services'); - const MemoryStorageService = require("./services/MemoryStorageService"); + const MemoryStorageService = require('./services/MemoryStorageService'); services.registerService('memory-storage', MemoryStorageService); } } diff --git a/src/backend/src/ThirdPartyDriversModule.js b/src/backend/src/ThirdPartyDriversModule.js index 69858c8959..847f63b124 100644 --- a/src/backend/src/ThirdPartyDriversModule.js +++ b/src/backend/src/ThirdPartyDriversModule.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class ThirdPartyDriversModule extends AdvancedBase { // constructor () { diff --git a/src/backend/src/annotatedobjects.js b/src/backend/src/annotatedobjects.js index e498ae3085..ad3d767ccc 100644 --- a/src/backend/src/annotatedobjects.js +++ b/src/backend/src/annotatedobjects.js @@ -6,14 +6,14 @@ // These annotated classes provide a solution to wrap plain objects. - class AnnotatedObject { constructor (o) { for ( const k in o ) this[k] = o[k]; } } -class object_returned_by_get_app extends AnnotatedObject {}; +class object_returned_by_get_app extends AnnotatedObject { +}; module.exports = { object_returned_by_get_app, diff --git a/src/backend/src/api/PathOrUIDValidator.js b/src/backend/src/api/PathOrUIDValidator.js index df47f45fec..6ec78b96c1 100644 --- a/src/backend/src/api/PathOrUIDValidator.js +++ b/src/backend/src/api/PathOrUIDValidator.js @@ -23,7 +23,7 @@ const _path = require('path'); * PathOrUIDValidator validates that either `path` or `uid` is present * in the request and requires a valid value for the parameter that was * used. Additionally, resolves the path if a path was provided. - * + * * @class PathOrUIDValidator * @static * @throws {APIError} if `path` and `uid` are both missing @@ -37,20 +37,30 @@ module.exports = class PathOrUIDValidator { const params = req.method === 'GET' ? req.query : req.body ; - if(!params.path && !params.uid) + if ( !params.path && !params.uid ) + { throw new APIError(400, '`path` or `uid` must be provided.'); + } // `path` must be a string - else if (params.path && !params.uid && typeof params.path !== 'string') + else if ( params.path && !params.uid && typeof params.path !== 'string' ) + { throw new APIError(400, '`path` must be a string.'); + } // `path` cannot be empty - else if(params.path && !params.uid && params.path.trim() === '') + else if ( params.path && !params.uid && params.path.trim() === '' ) + { throw new APIError(400, '`path` cannot be empty'); + } // `uid` must be a valid uuid - else if(params.uid && !params.path && !require('uuid').validate(params.uid)) + else if ( params.uid && !params.path && !require('uuid').validate(params.uid) ) + { throw new APIError(400, '`uid` must be a valid uuid'); + } // resolve path if provided - if(params.path) + if ( params.path ) + { params.path = _path.resolve('/', params.path); + } } }; diff --git a/src/backend/src/api/api_error_handler.js b/src/backend/src/api/api_error_handler.js index 64b0c41f18..c04ecffbdc 100644 --- a/src/backend/src/api/api_error_handler.js +++ b/src/backend/src/api/api_error_handler.js @@ -16,63 +16,63 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("./APIError"); +const APIError = require('./APIError'); /** * api_error_handler() is an express error handler for API errors. * It adheres to the express error handler signature and should be * used as the last middleware in an express app. - * + * * Since Express 5 is not yet released, this function is used by * eggspress() to handle errors instead of as a middleware. - * + * * @todo remove this function and use express error handling * when Express 5 is released - * - * @param {*} err - * @param {*} req - * @param {*} res - * @param {*} next - * @returns + * + * @param {*} err + * @param {*} req + * @param {*} res + * @param {*} next + * @returns */ module.exports = function (err, req, res, next) { - if (res.headersSent) { - console.error('error after headers were sent:', err); - return next(err) - } + if ( res.headersSent ) { + console.error('error after headers were sent:', err); + return next(err); + } + + // API errors might have a response to help the + // developer resolve the issue. + if ( err instanceof APIError ) { + return err.write(res); + } - // API errors might have a response to help the - // developer resolve the issue. - if ( err instanceof APIError ) { - return err.write(res); - } + if ( + typeof err === 'object' && + !(err instanceof Error) && + err.hasOwnProperty('message') + ) { + const apiError = APIError.create(400, err); + return apiError.write(res); + } - if ( - typeof err === 'object' && - ! (err instanceof Error) && - err.hasOwnProperty('message') - ) { - const apiError = APIError.create(400, err); - return apiError.write(res); - } + console.error('internal server error:', err); - console.error('internal server error:', err); + const services = globalThis.services; + if ( services && services.has('alarm') ) { + const alarm = services.get('alarm'); + alarm.create('api_error_handler', err.message, { + error: err, + url: req.url, + method: req.method, + body: req.body, + headers: req.headers, + }); + } - const services = globalThis.services; - if ( services && services.has('alarm') ) { - const alarm = services.get('alarm'); - alarm.create('api_error_handler', err.message, { - error: err, - url: req.url, - method: req.method, - body: req.body, - headers: req.headers, - }); - } + req.__error_handled = true; - req.__error_handled = true; - - // Other errors should provide as little information - // to the client as possible for security reasons. - return res.send(500, 'Internal Server Error'); + // Other errors should provide as little information + // to the client as possible for security reasons. + return res.send(500, 'Internal Server Error'); }; diff --git a/src/backend/src/api/eggspress.js b/src/backend/src/api/eggspress.js index ddba4d5962..859035264e 100644 --- a/src/backend/src/api/eggspress.js +++ b/src/backend/src/api/eggspress.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ diff --git a/src/backend/src/api/filesystem/FSNodeParam.js b/src/backend/src/api/filesystem/FSNodeParam.js index 3984142e56..d4c4e14aa4 100644 --- a/src/backend/src/api/filesystem/FSNodeParam.js +++ b/src/backend/src/api/filesystem/FSNodeParam.js @@ -16,11 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { is_valid_path } = require("../../filesystem/validation"); -const { is_valid_uuid4 } = require("../../helpers"); -const { Context } = require("../../util/context"); -const { PathBuilder } = require("../../util/pathutil"); -const APIError = require("../APIError"); +const { is_valid_path } = require('../../filesystem/validation'); +const { is_valid_uuid4 } = require('../../helpers'); +const { Context } = require('../../util/context'); +const { PathBuilder } = require('../../util/pathutil'); +const APIError = require('../APIError'); const _path = require('path'); module.exports = class FSNodeParam { @@ -49,12 +49,12 @@ module.exports = class FSNodeParam { }); } - if ( ! ['/','.','~'].includes(uidOrPath[0]) ) { + if ( ! ['/', '.', '~'].includes(uidOrPath[0]) ) { if ( is_valid_uuid4(uidOrPath) ) { return await fs.node({ uid: uidOrPath }); } - log.debug('tried uuid', { uidOrPath }) + log.debug('tried uuid', { uidOrPath }); throw APIError.create('field_invalid', null, { key: this.srckey, expected: 'unix-style path or uuid4', @@ -67,7 +67,7 @@ module.exports = class FSNodeParam { } if ( ! is_valid_path(uidOrPath) ) { - log.debug('tried path', { uidOrPath }) + log.debug('tried path', { uidOrPath }); throw APIError.create('field_invalid', null, { key: this.srckey, expected: 'unix-style path or uuid4', @@ -77,4 +77,4 @@ module.exports = class FSNodeParam { const resolved_path = PathBuilder.resolve(uidOrPath, { puterfs: true }); return await fs.node({ path: resolved_path }); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/backend/src/api/filesystem/FlagParam.js b/src/backend/src/api/filesystem/FlagParam.js index f19a9c6c39..d1db3a6bac 100644 --- a/src/backend/src/api/filesystem/FlagParam.js +++ b/src/backend/src/api/filesystem/FlagParam.js @@ -56,10 +56,10 @@ module.exports = class FlagParam { return value; } - log.debug('tried boolean', { value }) + log.debug('tried boolean', { value }); throw APIError.create('field_invalid', null, { key: this.srckey, expected: 'boolean', }); } -} +}; diff --git a/src/backend/src/api/filesystem/StringParam.js b/src/backend/src/api/filesystem/StringParam.js index 4757eef470..1d74aa0ac0 100644 --- a/src/backend/src/api/filesystem/StringParam.js +++ b/src/backend/src/api/filesystem/StringParam.js @@ -44,7 +44,7 @@ module.exports = class StringParam { } if ( typeof value !== 'string' ) { - log.debug('tried string', { value }) + log.debug('tried string', { value }); throw APIError.create('field_invalid', null, { key: this.srckey, expected: 'string', @@ -53,4 +53,4 @@ module.exports = class StringParam { return value; } -} +}; diff --git a/src/backend/src/api/filesystem/UserParam.js b/src/backend/src/api/filesystem/UserParam.js index 58c2d2cf0d..9d8d04c000 100644 --- a/src/backend/src/api/filesystem/UserParam.js +++ b/src/backend/src/api/filesystem/UserParam.js @@ -20,4 +20,4 @@ module.exports = class UserParam { consolidate ({ req }) { return req.user; } -} +}; diff --git a/src/backend/src/app.js b/src/backend/src/app.js index 02e0517cb5..0697bea9a9 100644 --- a/src/backend/src/app.js +++ b/src/backend/src/app.js @@ -17,4 +17,4 @@ require('dotenv').config(); * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -"use strict" +'use strict'; diff --git a/src/backend/src/boot/BootLogger.js b/src/backend/src/boot/BootLogger.js index 4dabe0b8de..dc6fccdfc4 100644 --- a/src/backend/src/boot/BootLogger.js +++ b/src/backend/src/boot/BootLogger.js @@ -18,26 +18,20 @@ */ class BootLogger { info (...args) { - console.log( - '\x1B[36;1m[BOOT/INFO]\x1B[0m', - ...args, - ); + console.log('\x1B[36;1m[BOOT/INFO]\x1B[0m', + ...args); } debug (...args) { if ( ! process.env.DEBUG ) return; console.log('\x1B[37m[BOOT/DEBUG]', ...args, '\x1B[0m'); } error (...args) { - console.log( - '\x1B[31;1m[BOOT/ERROR]\x1B[0m', - ...args, - ); + console.log('\x1B[31;1m[BOOT/ERROR]\x1B[0m', + ...args); } warn (...args) { - console.log( - '\x1B[33;1m[BOOT/WARN]\x1B[0m', - ...args, - ); + console.log('\x1B[33;1m[BOOT/WARN]\x1B[0m', + ...args); } } diff --git a/src/backend/src/boot/RuntimeEnvironment.js b/src/backend/src/boot/RuntimeEnvironment.js index 3d05ea8426..be4a90a223 100644 --- a/src/backend/src/boot/RuntimeEnvironment.js +++ b/src/backend/src/boot/RuntimeEnvironment.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); const { quot } = require('@heyputer/putility').libs.string; -const { TechnicalError } = require("../errors/TechnicalError"); -const { print_error_help } = require("../errors/error_help_details"); -const default_config = require("./default_config"); -const config = require("../config"); -const { ConfigLoader } = require("../config/ConfigLoader"); +const { TechnicalError } = require('../errors/TechnicalError'); +const { print_error_help } = require('../errors/error_help_details'); +const default_config = require('./default_config'); +const config = require('../config'); +const { ConfigLoader } = require('../config/ConfigLoader'); // highlights a string const hl = s => `\x1b[33;1m${s}\x1b[0m`; @@ -48,7 +48,7 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({ if ( path == undefined ) return false; const exists = fs.existsSync(path); - if ( !exists ) { + if ( ! exists ) { throw new Error(`Path does not exist: ${path}`); } @@ -93,8 +93,8 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({ throw new Error(`No valid config file found in path: ${path}`); }, env_not_set: name => () => { - return ! process.env[name]; - } + return !process.env[name]; + }, }); // Configuration paths in order of precedence. @@ -102,7 +102,9 @@ const path_checks = ({ logger }) => ({ fs, path_ }) => ({ const config_paths = ({ path_checks }) => ({ path_ }) => [ { label: '$CONFIG_PATH', - get path () { return process.env.CONFIG_PATH }, + get path () { + return process.env.CONFIG_PATH; + }, checks: [ path_checks.require_if_not_undefined, ], @@ -135,7 +137,9 @@ const valid_config_names = [ const runtime_paths = ({ path_checks }) => ({ path_ }) => [ { label: '$RUNTIME_PATH', - get path () { return process.env.RUNTIME_PATH }, + get path () { + return process.env.RUNTIME_PATH; + }, checks: [ path_checks.require_if_not_undefined, ], @@ -165,7 +169,9 @@ const runtime_paths = ({ path_checks }) => ({ path_ }) => [ const mod_paths = ({ path_checks, entry_path }) => ({ path_ }) => [ { label: '$MOD_PATH', - get path () { return process.env.MOD_PATH }, + get path () { + return process.env.MOD_PATH; + }, checks: [ path_checks.require_if_not_undefined, ], @@ -179,8 +185,7 @@ const mod_paths = ({ path_checks, entry_path }) => ({ path_ }) => [ }, { get path () { - return path_.join(path_.dirname( - entry_path || require.main.filename), '../mods'); + return path_.join(path_.dirname(entry_path || require.main.filename), '../mods'); }, checks: [ path_checks.skip_if_not_exists ], }, @@ -192,7 +197,7 @@ class RuntimeEnvironment extends AdvancedBase { path_: require('node:path'), crypto: require('node:crypto'), format: require('string-template'), - } + }; constructor ({ logger, entry_path, boot_parameters }) { super(); @@ -220,35 +225,28 @@ class RuntimeEnvironment extends AdvancedBase { // with some helpful values. A partial-population of this object later // in this function will be used when evaluating configured paths. const environment = {}; - environment.source = this.modules.path_.dirname( - this.entry_path || require.main.filename); + environment.source = this.modules.path_.dirname(this.entry_path || require.main.filename); environment.repo = this.modules.path_.dirname(environment.source); - const config_path_entry = this.get_first_suitable_path_( - { pathFor: 'configuration' }, - this.config_paths, - [ - this.path_checks.require_read_permission, - // this.path_checks.contains_config_file, - ] - ); + const config_path_entry = this.get_first_suitable_path_({ pathFor: 'configuration' }, + this.config_paths, + [ + this.path_checks.require_read_permission, + // this.path_checks.contains_config_file, + ]); // Note: there used to be a 'mods_path_entry' here too // but it was never used - const pwd_path_entry = this.get_first_suitable_path_( - { pathFor: 'working directory' }, - this.runtime_paths, - [ this.path_checks.require_write_permission ] - ); + const pwd_path_entry = this.get_first_suitable_path_({ pathFor: 'working directory' }, + this.runtime_paths, + [ this.path_checks.require_write_permission ]); process.chdir(pwd_path_entry.path); // Check for a valid config file in the config path let using_config; for ( const name of valid_config_names ) { - const exists = this.modules.fs.existsSync( - this.modules.path_.join(config_path_entry.path, name) - ); + const exists = this.modules.fs.existsSync(this.modules.path_.join(config_path_entry.path, name)); if ( exists ) { using_config = name; break; @@ -266,21 +264,15 @@ class RuntimeEnvironment extends AdvancedBase { generated_values.private_uid_secret = crypto.randomBytes(24).toString('hex'); generated_values.private_uid_namespace = crypto.randomUUID(); if ( using_config ) { - this.logger.debug( - `Overwriting ${quot(using_config)} because ` + - `${hl('--overwrite-config')} is set` - ); + this.logger.debug(`Overwriting ${quot(using_config)} because ` + + `${hl('--overwrite-config')} is set`); // make backup - fs.copyFileSync( - path_.join(config_path_entry.path, using_config), - path_.join(config_path_entry.path, using_config + '.bak'), - ); + fs.copyFileSync(path_.join(config_path_entry.path, using_config), + path_.join(config_path_entry.path, `${using_config }.bak`)); // preserve generated values { - const config_raw = fs.readFileSync( - path_.join(config_path_entry.path, using_config), - 'utf8', - ); + const config_raw = fs.readFileSync(path_.join(config_path_entry.path, using_config), + 'utf8'); const config_values = JSON.parse(config_raw); for ( const k in generated_values ) { if ( ! config_values[k] ) continue; @@ -292,33 +284,25 @@ class RuntimeEnvironment extends AdvancedBase { ...default_config, ...generated_values, }; - generated_config[""] = null; // for trailing comma - fs.writeFileSync( - path_.join(config_path_entry.path, 'config.json'), - JSON.stringify(generated_config, null, 4) + '\n', - ); + generated_config[''] = null; // for trailing comma + fs.writeFileSync(path_.join(config_path_entry.path, 'config.json'), + `${JSON.stringify(generated_config, null, 4) }\n`); using_config = 'config.json'; } let config_to_load = 'config.json'; if ( process.env.PUTER_CONFIG_PROFILE ) { - this.logger.debug( - hl('PROFILE') + ' ' + - quot(process.env.PUTER_CONFIG_PROFILE) + ' ' + - `because $PUTER_CONFIG_PROFILE is set` - ); - config_to_load = `${process.env.PUTER_CONFIG_PROFILE}.json` - const exists = fs.existsSync( - path_.join(config_path_entry.path, config_to_load) - ); + this.logger.debug(`${hl('PROFILE') } ${ + quot(process.env.PUTER_CONFIG_PROFILE) } ` + + 'because $PUTER_CONFIG_PROFILE is set'); + config_to_load = `${process.env.PUTER_CONFIG_PROFILE}.json`; + const exists = fs.existsSync(path_.join(config_path_entry.path, config_to_load)); if ( ! exists ) { - fs.writeFileSync( - path_.join(config_path_entry.path, config_to_load), - JSON.stringify({ - config_name: process.env.PUTER_CONFIG_PROFILE, - $imports: ['config.json'], - }, null, 4) + '\n', - ); + fs.writeFileSync(path_.join(config_path_entry.path, config_to_load), + `${JSON.stringify({ + config_name: process.env.PUTER_CONFIG_PROFILE, + $imports: ['config.json'], + }, null, 4) }\n`); } } @@ -330,7 +314,7 @@ class RuntimeEnvironment extends AdvancedBase { if ( ! config.config_name ) { throw new Error('config_name is required'); } - this.logger.debug(hl(`config name`) + ` ${quot(config.config_name)}`); + this.logger.debug(`${hl('config name') } ${quot(config.config_name)}`); const mod_paths = []; environment.mod_paths = mod_paths; @@ -346,9 +330,7 @@ class RuntimeEnvironment extends AdvancedBase { // If configured, add a user-specified mod path if ( config.mod_directories ) { for ( const dir of config.mod_directories ) { - const mods_directory = this.modules.format( - dir, environment, - ); + const mods_directory = this.modules.format(dir, environment); mod_paths.push(mods_directory); } } @@ -359,30 +341,22 @@ class RuntimeEnvironment extends AdvancedBase { get_first_suitable_path_ (meta, paths, last_checks) { for ( const entry of paths ) { const checks = [...(entry.checks ?? []), ...last_checks]; - this.logger.debug( - `Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...` - ); - + this.logger.debug(`Checking path ${quot(entry.label ?? entry.path)} for ${meta.pathFor}...`); + let checks_pass = true; for ( const check of checks ) { - this.logger.debug( - `-> doing ${quot(check.name)} on path ${quot(entry.path)}...` - ); + this.logger.debug(`-> doing ${quot(check.name)} on path ${quot(entry.path)}...`); const result = check(entry); if ( result === false ) { - this.logger.debug( - `-> ${quot(check.name)} doesn't like this path` - ); + this.logger.debug(`-> ${quot(check.name)} doesn't like this path`); checks_pass = false; break; } } - + if ( ! checks_pass ) continue; - this.logger.info( - `${hl(meta.pathFor)} ${quot(entry.path)}` - ) + this.logger.info(`${hl(meta.pathFor)} ${quot(entry.path)}`); return entry; } diff --git a/src/backend/src/boot/default_config.js b/src/backend/src/boot/default_config.js index 2b25bbc04b..d54564ed48 100644 --- a/src/backend/src/boot/default_config.js +++ b/src/backend/src/boot/default_config.js @@ -32,7 +32,7 @@ module.exports = { path: 'puter-database.sqlite', }, thumbnails: { - engine: 'purejs' + engine: 'purejs', }, 'file-cache': { disk_limit: 16384, @@ -40,6 +40,6 @@ module.exports = { precache_size: 16384, path: './file-cache', - } + }, }, }; diff --git a/src/backend/src/codex/CodeUtil.js b/src/backend/src/codex/CodeUtil.js index 9f5c5d3a62..fded9ef854 100644 --- a/src/backend/src/codex/CodeUtil.js +++ b/src/backend/src/codex/CodeUtil.js @@ -36,7 +36,7 @@ class CodeUtil { async _run () { return await method.call(this.self, this.values); } - } + }; Object.defineProperty(cls, 'name', { value: cls_name }); @@ -44,7 +44,7 @@ class CodeUtil { const op = new cls(); op.self = this; return await op.run(...a); - } + }; } } diff --git a/src/backend/src/codex/Sequence.js b/src/backend/src/codex/Sequence.js index a94824faf8..07477fa80d 100644 --- a/src/backend/src/codex/Sequence.js +++ b/src/backend/src/codex/Sequence.js @@ -105,7 +105,7 @@ * Supports conditional steps, deferred steps, and can be used as a runnable implementation for classes. * @class @extends Function */ -class Sequence { +class Sequence { /** * SequenceState represents the state of a Sequence execution. * Provides access to the sequence scope, step control, and utility methods for step functions. @@ -116,7 +116,7 @@ class Sequence { * @param {Sequence|function} sequence - The Sequence instance or its callable function. * @param {Object} [thisArg] - The instance to bind as `this` for step functions. */ - constructor(sequence, thisArg) { + constructor (sequence, thisArg) { if ( typeof sequence === 'function' ) { sequence = sequence.sequence; } @@ -138,7 +138,7 @@ class Sequence { * Get the current steps array for this sequence execution. * @returns {Array} The steps to execute. */ - get steps() { + get steps () { return this.steps_ ?? this.sequence_?.steps_; } @@ -147,7 +147,7 @@ class Sequence { * @param {Object} [values] - Initial values for the sequence scope. * @returns {Promise} */ - async run(values) { + async run (values) { // Initialize scope values = values || this.thisArg?.values || {}; Object.setPrototypeOf(this.scope_, values); @@ -162,7 +162,7 @@ class Sequence { }; } - if ( step.condition && ! await step.condition(this) ) { + if ( step.condition && !await step.condition(this) ) { continue; } @@ -203,7 +203,7 @@ class Sequence { * The first time defer is called, clones the steps and sets up for deferred insertion. * @param {function(Sequence.SequenceState): Promise} fn - The function to defer. */ - static defer_0 = function(fn) { + static defer_0 = function (fn) { this.steps_ = [...this.sequence_.steps_]; this.defer = this.constructor.defer_1; this.defer_ptr_ = this.steps_.length; @@ -213,7 +213,7 @@ class Sequence { * Subsequent calls to defer insert the function before the deferred pointer. * @param {function(Sequence.SequenceState): Promise} fn - The function to defer. */ - static defer_1 = function(fn) { + static defer_1 = function (fn) { // Deferred functions don't affect the return value const real_fn = fn; fn = async () => { @@ -230,7 +230,7 @@ class Sequence { * @param {string} k - The key to retrieve. * @returns {any} The value associated with the key. */ - get(k) { + get (k) { // TODO: record read1 return this.scope_[k]; } @@ -240,7 +240,7 @@ class Sequence { * @param {string} k - The key to set. * @param {any} v - The value to assign. */ - set(k, v) { + set (k, v) { // TODO: record mutation this.scope_[k] = v; } @@ -250,7 +250,7 @@ class Sequence { * @param {Object} [opt_itemsToSet] - Optional object of key-value pairs to set. * @returns {Object} Proxy to the current scope for value access. */ - values(opt_itemsToSet) { + values (opt_itemsToSet) { if ( opt_itemsToSet ) { for ( const k in opt_itemsToSet ) { this.set(k, opt_itemsToSet[k]); @@ -273,7 +273,7 @@ class Sequence { * @param {string} [k] - The property name to retrieve. If omitted, returns the instance. * @returns {any} The value from the instance or the instance itself. */ - iget(k) { + iget (k) { if ( k === undefined ) return this.thisArg; return this.thisArg?.[k]; } @@ -285,7 +285,7 @@ class Sequence { * @param {...any} args - Arguments to pass to the method. * @returns {any} The result of the method call. */ - icall(k, ...args) { + icall (k, ...args) { return this.thisArg?.[k]?.call(this.thisArg, ...args); } @@ -297,7 +297,7 @@ class Sequence { * @param {...any} args - Arguments to pass after the sequence state. * @returns {any} The result of the method call. */ - idcall(k, ...args) { + idcall (k, ...args) { return this.thisArg?.[k]?.call(this.thisArg, this, ...args); } @@ -305,7 +305,7 @@ class Sequence { * Get the logger from the instance, if available. * @returns {Object|undefined} The logger object. */ - get log() { + get log () { return this.iget('log'); } @@ -314,7 +314,7 @@ class Sequence { * @param {any} [return_value] - Value to return from the sequence. * @returns {any} The provided return value. */ - stop(return_value) { + stop (return_value) { this.stopped_ = true; return return_value; } @@ -333,7 +333,7 @@ class Sequence { * - Options object may include `name`, `record_history`, `before_each`, `after_each`. * @returns {SequenceCallable} A callable function that runs the sequence. */ - constructor(...args) { + constructor (...args) { const sequence = this; const steps = []; @@ -356,7 +356,7 @@ class Sequence { * @param {Object|Sequence.SequenceState} [opt_values] - Initial values or a SequenceState. * @returns {Promise} The return value of the last step. */ - const fn = async function(opt_values) { + const fn = async function (opt_values) { if ( opt_values && opt_values instanceof Sequence.SequenceState ) { opt_values = opt_values.scope_; } diff --git a/src/backend/src/config.js b/src/backend/src/config.js index 6fa4d4652b..d908e2faf3 100644 --- a/src/backend/src/config.js +++ b/src/backend/src/config.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -"use strict" +'use strict'; const deep_proto_merge = require('./config/deep_proto_merge'); // const reserved_words = require('./config/reserved_words'); @@ -53,9 +53,9 @@ config.kv_max_value_size = 400 * 1024; // Captcha configuration config.captcha = { - enabled: false, // Enable captcha by default + enabled: false, // Enable captcha by default expirationTime: 10 * 60 * 1000, // 10 minutes default expiration time - difficulty: 'medium' // Default difficulty level + difficulty: 'medium', // Default difficulty level }; config.monitor = { @@ -64,7 +64,7 @@ config.monitor = { }; config.max_subdomains_per_user = 2000; -config.storage_capacity = 1*1024*1024*1024; +config.storage_capacity = 1 * 1024 * 1024 * 1024; config.static_hosting_domain = 'site.puter.localhost'; // Storage limiting is set to false by default @@ -74,11 +74,11 @@ config.available_device_storage = null; config.thumb_width = 80; config.thumb_height = 80; -config.app_max_icon_size = 5*1024*1024; +config.app_max_icon_size = 5 * 1024 * 1024; config.defaultjs_asset_path = '../../'; -config.short_description = `Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.`; +config.short_description = 'Puter is a privacy-first personal cloud that houses all your files, apps, and games in one private and secure place, accessible from anywhere at any time.'; config.title = 'Puter'; config.company = 'Puter Technologies Inc.'; @@ -103,18 +103,18 @@ config.reserved_words = []; } // set default S3 settings for this server, if any -if (config.server_id) { - // see if this server has a specific bucket +if ( config.server_id ) { + // see if this server has a specific bucket for ( const server of config.servers ) { if ( server.id !== config.server_id ) continue; if ( ! server.s3_bucket ) continue; config.s3_bucket = server.s3_bucket; config.s3_region = server.region; - } + } } -config.contact_email = 'hey@' + config.domain; +config.contact_email = `hey@${ config.domain}`; // TODO: default value will be changed to false in a future release; // details to follow in a future announcement. @@ -154,14 +154,14 @@ module.exports = config; // NEW_CONFIG_LOADING const maybe_port = config => - config.pub_port !== 80 && config.pub_port !== 443 ? ':' + config.pub_port : ''; + config.pub_port !== 80 && config.pub_port !== 443 ? `:${ config.pub_port}` : ''; const computed_defaults = { pub_port: config => config.http_port, - origin: config => config.protocol + '://' + config.domain + maybe_port(config), + origin: config => `${config.protocol }://${ config.domain }${maybe_port(config)}`, api_base_url: config => config.experimental_no_subdomain ? config.origin - : config.protocol + '://api.' + config.domain + maybe_port(config), + : `${config.protocol }://api.${ config.domain }${maybe_port(config)}`, social_card: config => `${config.origin}/assets/img/screenshot.png`, }; @@ -186,7 +186,7 @@ const config_pointer = {}; }; replacement_config = deep_proto_merge(replacement_config, Object.getPrototypeOf(config_pointer), { preserve_flag: true, - }) + }); Object.setPrototypeOf(config_pointer, replacement_config); }; @@ -198,37 +198,37 @@ const config_pointer = {}; // We have some values with computed defaults { const get_implied = (target, prop) => { - if (prop in computed_defaults) { + if ( prop in computed_defaults ) { return computed_defaults[prop](target); } return undefined; }; config_to_export = new Proxy(config_to_export, { get: (target, prop, receiver) => { - if (prop in target) { + if ( prop in target ) { return target[prop]; } else { return get_implied(config_to_export, prop); } - } - }) + }, + }); } // We'd like to store values changed at runtime separately // for easier runtime debugging { const config_runtime_values = { - $: 'runtime-values' + $: 'runtime-values', }; let initialPrototype = config_to_export; Object.setPrototypeOf(config_runtime_values, config_to_export); - config_to_export = config_runtime_values - + config_to_export = config_runtime_values; + config_to_export.__set_config_object__ = (object, options = {}) => { // options for this method const replacePrototype = options.replacePrototype ?? true; const useInitialPrototype = options.useInitialPrototype ?? true; - + // maybe replace prototype if ( replacePrototype ) { const newProto = useInitialPrototype @@ -236,7 +236,7 @@ const config_pointer = {}; : Object.getPrototypeOf(config_runtime_values); Object.setPrototypeOf(object, newProto); } - + // use this object as the prototype Object.setPrototypeOf(config_runtime_values, object); }; @@ -247,14 +247,14 @@ const config_pointer = {}; set: (target, prop, value, receiver) => { const logger = Context.get('logger', { allow_fallback: true }); // If no logger, just give up - if ( logger ) logger.debug( - '\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m', - { prop, value }, - ); + if ( logger ) { + logger.debug('\x1B[36;1mCONFIGURATION MUTATED AT RUNTIME\x1B[0m', + { prop, value }); + } target[prop] = value; return true; - } - }) + }, + }); } // We configure the behavior in context.js from here to avoid a cyclic diff --git a/src/backend/src/config/ConfigLoader.js b/src/backend/src/config/ConfigLoader.js index 1295812560..0485e94c3c 100644 --- a/src/backend/src/config/ConfigLoader.js +++ b/src/backend/src/config/ConfigLoader.js @@ -16,14 +16,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); const { quot } = require('@heyputer/putility').libs.string; class ConfigLoader extends AdvancedBase { static MODULES = { - path_: require("path"), - fs: require("fs"), - } + path_: require('path'), + fs: require('fs'), + }; constructor (logger, path, config) { super(); @@ -47,10 +47,8 @@ class ConfigLoader extends AdvancedBase { delete config_values.$requires; this.apply_requires(this.path, config_list, { by: name }); } - this.logger.debug( - `Applying config: ${path_.relative(this.path, config_path)}` + - (meta.by ? ` (required by ${meta.by})` : '') - ); + this.logger.debug(`Applying config: ${path_.relative(this.path, config_path)}${ + meta.by ? ` (required by ${meta.by})` : ''}`); this.config.load_config(config_values); } diff --git a/src/backend/src/config/deep_proto_merge.js b/src/backend/src/config/deep_proto_merge.js index f8da5744d3..a2032112b4 100644 --- a/src/backend/src/config/deep_proto_merge.js +++ b/src/backend/src/config/deep_proto_merge.js @@ -39,15 +39,13 @@ const deep_proto_merge = (replacement, delegate, options) => { for ( const key in replacement ) { if ( ! is_object(replacement[key]) ) continue; - if ( options?.preserve_flag && ! replacement[key].$preserve ) { + if ( options?.preserve_flag && !replacement[key].$preserve ) { continue; } if ( ! is_object(delegate[key]) ) { continue; } - replacement[key] = deep_proto_merge( - replacement[key], delegate[key], options, - ); + replacement[key] = deep_proto_merge(replacement[key], delegate[key], options); } // use a Proxy object to ensure all keys are present @@ -62,10 +60,10 @@ const deep_proto_merge = (replacement, delegate, options) => { // Combine and deduplicate properties using a Set, then convert back to an array const s = new Set([ ...protoProps, - ...ownProps + ...ownProps, ]); - if (options?.preserve_flag) { + if ( options?.preserve_flag ) { // remove $preserve if it exists s.delete('$preserve'); } @@ -76,16 +74,16 @@ const deep_proto_merge = (replacement, delegate, options) => { // Real descriptor let descriptor = Object.getOwnPropertyDescriptor(target, prop); - if (descriptor) return descriptor; + if ( descriptor ) return descriptor; // Immediate prototype descriptor const proto = Object.getPrototypeOf(target); descriptor = Object.getOwnPropertyDescriptor(proto, prop); - if (descriptor) return descriptor; + if ( descriptor ) return descriptor; return undefined; - } + }, }); diff --git a/src/backend/src/data/hardcoded-permissions.js b/src/backend/src/data/hardcoded-permissions.js index 984c6456d6..e9bc3e675c 100644 --- a/src/backend/src/data/hardcoded-permissions.js +++ b/src/backend/src/data/hardcoded-permissions.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -92,7 +92,7 @@ const policy_perm = selector => ({ $: 'json-address', path: '/admin/.policy/drivers.json', selector, - } + }, }); const hardcoded_user_group_permissions = { diff --git a/src/backend/src/definitions/Library.js b/src/backend/src/definitions/Library.js index 9c7aebf435..ffe8526879 100644 --- a/src/backend/src/definitions/Library.js +++ b/src/backend/src/definitions/Library.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../services/BaseService"); +const BaseService = require('../services/BaseService'); class Library extends BaseService { // diff --git a/src/backend/src/definitions/SimpleEntity.js b/src/backend/src/definitions/SimpleEntity.js index 269b932c75..d49c29ba5f 100644 --- a/src/backend/src/definitions/SimpleEntity.js +++ b/src/backend/src/definitions/SimpleEntity.js @@ -16,21 +16,21 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { Context } = require("../util/context"); +const { Context } = require('../util/context'); module.exports = function SimpleEntity ({ name, methods, fetchers }) { const create = function (values) { const entity = { values }; Object.assign(entity, methods); for ( const fetcher_name in fetchers ) { - entity['fetch_' + fetcher_name] = async function () { + entity[`fetch_${ fetcher_name}`] = async function () { if ( this.values.hasOwnProperty(fetcher_name) ) { return this.values[fetcher_name]; } const value = await fetchers[fetcher_name].call(this); this.values[fetcher_name] = value; return value; - } + }; } entity.context = values.context ?? Context.get(); entity.services = entity.context.get('services'); diff --git a/src/backend/src/entities/Group.js b/src/backend/src/entities/Group.js index 0e24f33a5f..454be85ed6 100644 --- a/src/backend/src/entities/Group.js +++ b/src/backend/src/entities/Group.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const SimpleEntity = require("../definitions/SimpleEntity"); +const SimpleEntity = require('../definitions/SimpleEntity'); module.exports = SimpleEntity({ name: 'group', @@ -25,7 +25,7 @@ module.exports = SimpleEntity({ const svc_group = this.services.get('group'); const members = await svc_group.list_members({ uid: this.values.uid }); return members; - } + }, }, methods: { async get_client_value (options = {}) { @@ -38,6 +38,6 @@ module.exports = SimpleEntity({ ...(options.members ? { members: this.values.members } : {}), }; return group; - } - } + }, + }, }); diff --git a/src/backend/src/errors/TechnicalError.js b/src/backend/src/errors/TechnicalError.js index 596c837afc..593c973157 100644 --- a/src/backend/src/errors/TechnicalError.js +++ b/src/backend/src/errors/TechnicalError.js @@ -37,7 +37,7 @@ class TechnicalError extends Error { const ERR_HINT_NOSTACK = e => { e.toString = () => e.message; -} +}; module.exports = { TechnicalError, diff --git a/src/backend/src/errors/error_help_details.js b/src/backend/src/errors/error_help_details.js index a55867d63e..a6cfaae34e 100644 --- a/src/backend/src/errors/error_help_details.js +++ b/src/backend/src/errors/error_help_details.js @@ -34,12 +34,12 @@ const reused = { subject: 'RuntimeEnvironment.js', location: 'src/boot/ in repository', use: 'code that performs the checks', - } - ] + }, + ], }; const programmer_errors = [ - 'Assignment to constant variable.' + 'Assignment to constant variable.', ]; const error_help_details = [ @@ -51,7 +51,7 @@ const error_help_details = [ more.references = [ ...reused.runtime_env_references, ]; - } + }, }, { match: ({ message }) => ( @@ -72,7 +72,7 @@ const error_help_details = [ more.references = [ ...reused.runtime_env_references, ]; - } + }, }, { match: ({ message }) => ( @@ -84,21 +84,19 @@ const error_help_details = [ title: 'Create a valid config file', }, ]; - } + }, }, { match: ({ message }) => ( - message === `config_name is required` + message === 'config_name is required' ), apply (more) { more.solutions = [ 'ensure config_name is present in your config file', - 'Seek help on ' + osclink( - 'https://discord.gg/PQcx7Teh8u', - 'our Discord server' - ), + `Seek help on ${ osclink('https://discord.gg/PQcx7Teh8u', + 'our Discord server')}`, ]; - } + }, }, { match: ({ message }) => ( @@ -110,10 +108,10 @@ const error_help_details = [ subject: 'MDN Reference for this error', location: 'on the internet', use: 'describes why this error occurs', - url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment' + url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Invalid_const_assignment', }, ]; - } + }, }, { match: ({ message }) => ( @@ -125,18 +123,16 @@ const error_help_details = [ ]; more.solutions = [ { - title: `Check for an issue on ` + - osclink('https://github.com/HeyPuter/puter/issues') + title: `Check for an issue on ${ + osclink('https://github.com/HeyPuter/puter/issues')}`, }, { - title: `If there's no issue, please ` + - osclink( - 'https://github.com/HeyPuter/puter/issues/new', - 'create one' - ) + '.' - } + title: `If there's no issue, please ${ + osclink('https://github.com/HeyPuter/puter/issues/new', + 'create one') }.`, + }, ]; - } + }, }, { match: ({ message }) => ( @@ -146,8 +142,8 @@ const error_help_details = [ more.notes = [ 'There might be a trailing-comma in your config', ]; - } - } + }, + }, ]; /** @@ -178,13 +174,13 @@ const print_error_help = (err, out = process.stdout) => { const wrap_list_title = s => `\x1B[36;1m${s}:\x1B[0m`; - write(wrap_msg(err.message) + '\n'); + write(`${wrap_msg(err.message) }\n`); - write = (s) => out.write('\x1B[31;1m┃\x1B[0m ' + s); + write = (s) => out.write(`\x1B[31;1m┃\x1B[0m ${ s}`); const vis = (stok, etok, str) => { return `\x1B[36;1m${stok}\x1B[0m${str}\x1B[36;1m${etok}\x1B[0m`; - } + }; let lf_sep = false; @@ -204,9 +200,9 @@ const print_error_help = (err, out = process.stdout) => { if ( lf_sep ) write('\n'); lf_sep = true; any_help = true; - write('The suggestions below may help resolve this issue.\n') + write('The suggestions below may help resolve this issue.\n'); write('\n'); - write(wrap_list_title('Possible Solutions') + '\n'); + write(`${wrap_list_title('Possible Solutions') }\n`); for ( const sol of err.more.solutions ) { write(` - ${sol.title}\n`); } @@ -216,9 +212,9 @@ const print_error_help = (err, out = process.stdout) => { if ( lf_sep ) write('\n'); lf_sep = true; any_help = true; - write('The references below may be related to this issue.\n') + write('The references below may be related to this issue.\n'); write('\n'); - write(wrap_list_title('References') + '\n'); + write(`${wrap_list_title('References') }\n`); for ( const ref of err.more.references ) { write(` - ${vis('[', ']', ref.subject)} ` + `${vis('(', ')', ref.location)};\n`); @@ -234,9 +230,9 @@ const print_error_help = (err, out = process.stdout) => { write('Help can be added in src/errors/error_help_details.\n'); } - out.write(`\x1B[31;1m┗━━ [ END HELP ]\x1B[0m\n`) + out.write('\x1B[31;1m┗━━ [ END HELP ]\x1B[0m\n'); out.write('\n'); -} +}; module.exports = { error_help_details, diff --git a/src/backend/src/extension/RuntimeModule.js b/src/backend/src/extension/RuntimeModule.js index ab6f11d715..c39a849353 100644 --- a/src/backend/src/extension/RuntimeModule.js +++ b/src/backend/src/extension/RuntimeModule.js @@ -1,4 +1,4 @@ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class RuntimeModule extends AdvancedBase { constructor (options = {}) { @@ -6,7 +6,7 @@ class RuntimeModule extends AdvancedBase { this.exports_ = undefined; this.exports_is_set_ = false; this.remappings = options.remappings ?? {}; - + this.name = options.name ?? undefined; } set exports (value) { diff --git a/src/backend/src/extension/RuntimeModuleRegistry.js b/src/backend/src/extension/RuntimeModuleRegistry.js index 59ac1a9055..27968f7f7c 100644 --- a/src/backend/src/extension/RuntimeModuleRegistry.js +++ b/src/backend/src/extension/RuntimeModuleRegistry.js @@ -1,12 +1,12 @@ -const { AdvancedBase } = require("@heyputer/putility"); -const { RuntimeModule } = require("./RuntimeModule"); +const { AdvancedBase } = require('@heyputer/putility'); +const { RuntimeModule } = require('./RuntimeModule'); class RuntimeModuleRegistry extends AdvancedBase { constructor () { super(); this.modules_ = {}; } - + register (extensionModule, options = {}) { if ( ! (extensionModule instanceof RuntimeModule) ) { throw new Error(`expected a RuntimeModule, but got: ${ @@ -19,7 +19,7 @@ class RuntimeModuleRegistry extends AdvancedBase { this.modules_[uniqueName] = extensionModule; extensionModule.runtimeModuleRegistry = this; } - + exportsOf (name) { if ( ! this.modules_[name] ) { throw new Error(`could not find runtime module: ${name}`); @@ -29,5 +29,5 @@ class RuntimeModuleRegistry extends AdvancedBase { } module.exports = { - RuntimeModuleRegistry + RuntimeModuleRegistry, }; diff --git a/src/backend/src/filesystem/ECMAP.js b/src/backend/src/filesystem/ECMAP.js index 8edb871cb6..5d68795c5f 100644 --- a/src/backend/src/filesystem/ECMAP.js +++ b/src/backend/src/filesystem/ECMAP.js @@ -1,5 +1,5 @@ -const { Context } = require("../util/context"); -const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require("./node/selectors"); +const { Context } = require('../util/context'); +const { NodeUIDSelector, NodePathSelector, NodeInternalIDSelector } = require('./node/selectors'); const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m'; @@ -8,28 +8,28 @@ const LOG_PREFIX = '\x1B[31;1m[[\x1B[33;1mEC\x1B[32;1mMAP\x1B[31;1m]]\x1B[0m'; * whenever it is present in the execution context (AsyncLocalStorage). * It is assumed that this object is transient and invalidation of stale * entries is not necessary. - * + * * The name ECMAP simple means Execution Context Map, because the map * exists in memory at a particular frame of the execution context. */ class ECMAP { static SYMBOL = Symbol('ECMAP'); - + constructor () { this.identifier = require('uuid').v4(); - + // entry caches this.uuid_to_fsNodeContext = {}; this.path_to_fsNodeContext = {}; this.id_to_fsNodeContext = {}; - + // identifier association caches this.path_to_uuid = {}; this.uuid_to_path = {}; - + this.unlinked = false; } - + /** * unlink() clears all references from this ECMAP to ensure that it will be * GC'd. This is called by ECMAP.arun() after the callback has resolved. @@ -42,16 +42,16 @@ class ECMAP { this.path_to_uuid = null; this.uuid_to_path = null; } - + get logPrefix () { return `${LOG_PREFIX} \x1B[36[1m${this.identifier}\x1B[0m`; } - + log (...a) { if ( ! process.env.LOG_ECMAP ) return; console.log(this.logPrefix, ...a); } - + get_fsNodeContext_from_selector (selector) { if ( this.unlinked ) return null; @@ -61,21 +61,21 @@ class ECMAP { if ( selector instanceof NodeUIDSelector ) { value = this.uuid_to_fsNodeContext[selector.value]; if ( value ) return value; - + let maybe_path = this.uuid_to_path[value]; if ( ! maybe_path ) return; value = this.path_to_fsNodeContext[maybe_path]; if ( value ) return value; } else - if ( selector instanceof NodePathSelector ) { - value = this.path_to_fsNodeContext[selector.value]; - if ( value ) return value; - - let maybe_uid = this.path_to_uuid[value]; - value = this.uuid_to_fsNodeContext[maybe_uid]; - if ( value ) return value; - } + if ( selector instanceof NodePathSelector ) { + value = this.path_to_fsNodeContext[selector.value]; + if ( value ) return value; + + let maybe_uid = this.path_to_uuid[value]; + value = this.uuid_to_fsNodeContext[maybe_uid]; + if ( value ) return value; + } })(); if ( retvalue ) { this.log('\x1B[32;1m <<<<< ECMAP HIT >>>>> \x1B[0m'); @@ -84,7 +84,7 @@ class ECMAP { } return retvalue; } - + store_fsNodeContext_to_selector (selector, node) { if ( this.unlinked ) return null; @@ -96,16 +96,16 @@ class ECMAP { this.path_to_fsNodeContext[selector.value] = node; } if ( selector instanceof NodeInternalIDSelector ) { - this.id_to_fsNodeContext[selector.service+':'+selector.id] = node; + this.id_to_fsNodeContext[`${selector.service}:${selector.id}`] = node; } } - + store_fsNodeContext (node) { if ( this.unlinked ) return; this.store_fsNodeContext_to_selector(node.selector, node); } - + static async arun (cb) { let context = Context.get(); if ( ! context.get(this.SYMBOL) ) { diff --git a/src/backend/src/filesystem/FSNodeContext.js b/src/backend/src/filesystem/FSNodeContext.js index 5f7e0fa69f..e21088c0fb 100644 --- a/src/backend/src/filesystem/FSNodeContext.js +++ b/src/backend/src/filesystem/FSNodeContext.js @@ -16,19 +16,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { get_user, id2path, id2uuid, is_empty, suggest_app_for_fsentry, get_app } = require("../helpers"); +const { get_user, id2path, id2uuid, is_empty, suggest_app_for_fsentry, get_app } = require('../helpers'); const putility = require('@heyputer/putility'); -const config = require("../config"); +const config = require('../config'); const _path = require('path'); -const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require("./node/selectors"); -const { Context } = require("../util/context"); -const { NodeRawEntrySelector } = require("./node/selectors"); -const { DB_READ } = require("../services/database/consts"); -const { UserActorType, AppUnderUserActorType, Actor } = require("../services/auth/Actor"); -const { PermissionUtil } = require("../services/auth/permissionUtils.mjs"); -const { ECMAP } = require("./ECMAP"); -const { MANAGE_PERM_PREFIX } = require("../services/auth/permissionConts.mjs"); +const { NodeInternalIDSelector, NodeChildSelector, NodeUIDSelector, RootNodeSelector, NodePathSelector } = require('./node/selectors'); +const { Context } = require('../util/context'); +const { NodeRawEntrySelector } = require('./node/selectors'); +const { DB_READ } = require('../services/database/consts'); +const { UserActorType, AppUnderUserActorType, Actor } = require('../services/auth/Actor'); +const { PermissionUtil } = require('../services/auth/permissionUtils.mjs'); +const { ECMAP } = require('./ECMAP'); +const { MANAGE_PERM_PREFIX } = require('../services/auth/permissionConts.mjs'); /** * Container for information collected about a node @@ -73,7 +73,7 @@ module.exports = class FSNodeContext { * @param {*} opt_identifier.id please pass mysql_id instead * @param {*} opt_identifier.mysql_id a MySQL ID of the filesystem entry */ - constructor({ + constructor ({ services, selector, provider, @@ -147,7 +147,7 @@ module.exports = class FSNodeContext { } } - set selector(new_selector) { + set selector (new_selector) { // Only add the selector if we don't already have it for ( const selector of this.selectors_ ) { if ( selector instanceof new_selector.constructor ) return; @@ -162,11 +162,11 @@ module.exports = class FSNodeContext { this.selector_ = new_selector; } - get selector() { + get selector () { return this.get_optimal_selector(); } - get_selector_of_type(cls) { + get_selector_of_type (cls) { // Reverse iterate over selectors for ( let i = this.selectors_.length - 1; i >= 0; i-- ) { const selector = this.selectors_[i]; @@ -182,7 +182,7 @@ module.exports = class FSNodeContext { return null; } - get_optimal_selector() { + get_optimal_selector () { for ( const cls of FSNodeContext.SELECTOR_PRIORITY_ORDER ) { const selector = this.get_selector_of_type(cls); if ( selector ) return selector; @@ -191,21 +191,21 @@ module.exports = class FSNodeContext { return this.selector_; } - get isRoot() { + get isRoot () { return this.path === '/'; } - async isUserDirectory() { + async isUserDirectory () { if ( this.isRoot ) return false; if ( this.found === undefined ) { await this.fetchEntry(); } if ( this.isRoot ) return false; if ( this.found === false ) return undefined; - return ! this.entry.parent_uid; + return !this.entry.parent_uid; } - async isAppDataDirectory() { + async isAppDataDirectory () { if ( this.isRoot ) return false; if ( this.found === undefined ) { await this.fetchEntry(); @@ -217,7 +217,7 @@ module.exports = class FSNodeContext { return components[1] === 'AppData'; } - async isPublic() { + async isPublic () { if ( this.isRoot ) return false; const components = await this.getPathComponents(); if ( await this.isUserDirectory() ) return false; @@ -225,7 +225,7 @@ module.exports = class FSNodeContext { return false; } - async getPathComponents() { + async getPathComponents () { if ( this.isRoot ) return []; // We can get path components for non-existing nodes if they @@ -245,30 +245,30 @@ module.exports = class FSNodeContext { return path.split('/'); } - async getUserPart() { + async getUserPart () { if ( this.isRoot ) return; const components = await this.getPathComponents(); return components[0]; } - async getPathSize() { + async getPathSize () { if ( this.isRoot ) return; const components = await this.getPathComponents(); return components.length; } - async exists({ fetch_options } = {}) { + async exists ({ fetch_options } = {}) { await this.fetchEntry(fetch_options); if ( ! this.found ) { - this.log.debug('here\'s why it doesn\'t exist: ' + - this.selector.describe() + ' -> ' + - this.uid + ' ' + - JSON.stringify(this.entry, null, ' ')); + this.log.debug(`here's why it doesn't exist: ${ + this.selector.describe() } -> ${ + this.uid } ${ + JSON.stringify(this.entry, null, ' ')}`); } return this.found; } - async fetchPath() { + async fetchPath () { if ( this.path ) return; this.path = await this.services.get('information') @@ -299,10 +299,10 @@ module.exports = class FSNodeContext { if ( this.found === true && - ! fetch_entry_options.force && + !fetch_entry_options.force && ( // thumbnail already fetched, or not asked for - ! fetch_entry_options.thumbnail || this.entry?.thumbnail || + !fetch_entry_options.thumbnail || this.entry?.thumbnail || this.found_thumbnail !== undefined ) ) { @@ -319,7 +319,7 @@ module.exports = class FSNodeContext { }, }; - this.log.debug('fetching entry: ' + this.selector.describe()); + this.log.debug(`fetching entry: ${ this.selector.describe()}`); const entry = await this.provider.stat({ selector: this.selector, @@ -334,19 +334,19 @@ module.exports = class FSNodeContext { } else { this.found = true; - if ( ! this.uid && entry.uuid ) { + if ( !this.uid && entry.uuid ) { this.uid = entry.uuid; } - if ( ! this.mysql_id && entry.id ) { + if ( !this.mysql_id && entry.id ) { this.mysql_id = entry.id; } - if ( ! this.path && entry.path ) { + if ( !this.path && entry.path ) { this.path = entry.path; } - if ( ! this.name && entry.name ) { + if ( !this.name && entry.name ) { this.name = entry.name; } @@ -365,7 +365,7 @@ module.exports = class FSNodeContext { * * This just calls ResourceService under the hood. */ - async awaitStableEntry() { + async awaitStableEntry () { const resourceService = Context.get('services').get('resourceService'); await resourceService.waitForResource(this.selector); } @@ -378,19 +378,19 @@ module.exports = class FSNodeContext { * * @param fs:decouple-subdomains */ - async fetchSubdomains(user, _force) { + async fetchSubdomains (user, _force) { if ( ! this.entry.is_dir ) return; const db = this.services.get('database').get(DB_READ, 'filesystem'); this.entry.subdomains = []; - let subdomains = await db.read(`SELECT * FROM subdomains WHERE root_dir_id = ? AND user_id = ?`, + let subdomains = await db.read('SELECT * FROM subdomains WHERE root_dir_id = ? AND user_id = ?', [this.entry.id, user.id]); - if ( subdomains.length > 0 ){ + if ( subdomains.length > 0 ) { subdomains.forEach((sd) => { this.entry.subdomains.push({ subdomain: sd.subdomain, - address: config.protocol + '://' + sd.subdomain + "." + 'puter.site', + address: `${config.protocol }://${ sd.subdomain }.` + 'puter.site', uuid: sd.uuid, }); }); @@ -403,7 +403,7 @@ module.exports = class FSNodeContext { * `owner` property of the fsentry. * @param {bool} force fetch owner if it was already fetched */ - async fetchOwner(_force) { + async fetchOwner (_force) { if ( this.isRoot ) return; const owner = await get_user({ id: this.entry.user_id }); this.entry.owner = { @@ -418,8 +418,8 @@ module.exports = class FSNodeContext { * of the fsentry. * @param {bool} force fetch shares if they were already fetched */ - async fetchShares(force) { - if ( this.entry.shares && ! force ) return; + async fetchShares (force) { + if ( this.entry.shares && !force ) return; const actor = Context.get('actor'); if ( ! actor ) { @@ -503,12 +503,12 @@ module.exports = class FSNodeContext { * * @todo fs:decouple-versions */ - async fetchVersions(force) { - if ( this.entry.versions && ! force ) return; + async fetchVersions (force) { + if ( this.entry.versions && !force ) return; const db = this.services.get('database').get(DB_READ, 'filesystem'); - let versions = await db.read(`SELECT * FROM fsentry_versions WHERE fsentry_id = ?`, + let versions = await db.read('SELECT * FROM fsentry_versions WHERE fsentry_id = ?', [this.entry.id]); const versions_tidy = []; for ( const version of versions ) { @@ -530,7 +530,7 @@ module.exports = class FSNodeContext { * Fetches the size of a file or directory if it was not * already fetched. */ - async fetchSize() { + async fetchSize () { // we already have the size for files if ( ! this.entry.is_dir ) { await this.fetchEntry(); @@ -542,8 +542,8 @@ module.exports = class FSNodeContext { return this.entry.size; } - async fetchSuggestedApps(user, force) { - if ( this.entry.suggested_apps && ! force ) return; + async fetchSuggestedApps (user, force) { + if ( this.entry.suggested_apps && !force ) return; await this.fetchEntry(); if ( ! this.entry ) return; @@ -552,15 +552,15 @@ module.exports = class FSNodeContext { await suggest_app_for_fsentry(this.entry, { user }); } - async fetchIsEmpty() { - if ( ! this.uid && ! this.path ) return; + async fetchIsEmpty () { + if ( !this.uid && !this.path ) return; this.entry.is_empty = await is_empty({ uid: this.uid, path: this.path, }); } - async fetchAll(_fsEntryFetcher, user, _force) { + async fetchAll (_fsEntryFetcher, user, _force) { await this.fetchEntry({ thumbnail: true }); await this.fetchSubdomains(user); await this.fetchOwner(); @@ -571,7 +571,7 @@ module.exports = class FSNodeContext { await this.fetchIsEmpty(); } - async get(key) { + async get (key) { /* This isn't supposed to stay like this! @@ -585,15 +585,15 @@ module.exports = class FSNodeContext { */ if ( this.found === false ) { - throw new Error(`Tried to get ${key} of non-existent fsentry: ` + - this.selector.describe(true)); + throw new Error(`Tried to get ${key} of non-existent fsentry: ${ + this.selector.describe(true)}`); } if ( key === 'entry' ) { await this.fetchEntry(); if ( this.found === false ) { - throw new Error(`Tried to get entry of non-existent fsentry: ` + - this.selector.describe(true)); + throw new Error(`Tried to get entry of non-existent fsentry: ${ + this.selector.describe(true)}`); } return this.entry; } @@ -601,14 +601,14 @@ module.exports = class FSNodeContext { if ( key === 'path' ) { if ( ! this.path ) await this.fetchEntry(); if ( this.found === false ) { - throw new Error(`Tried to get path of non-existent fsentry: ` + - this.selector.describe(true)); + throw new Error(`Tried to get path of non-existent fsentry: ${ + this.selector.describe(true)}`); } if ( ! this.path ) { await this.fetchPath(); } if ( ! this.path ) { - throw new Error(`failed to get path`); + throw new Error('failed to get path'); } return this.path; } @@ -642,8 +642,8 @@ module.exports = class FSNodeContext { if ( key === k ) { await this.fetchEntry(); if ( this.found === false ) { - throw new Error(`Tried to get ${key} of non-existent fsentry: ` + - this.selector.describe(true)); + throw new Error(`Tried to get ${key} of non-existent fsentry: ${ + this.selector.describe(true)}`); } return this.entry[k]; } @@ -700,7 +700,7 @@ module.exports = class FSNodeContext { throw new Error(`unrecognize key for FSNodeContext.get: ${key}`); } - async getParent() { + async getParent () { if ( this.isRoot ) { throw new Error('tried to get parent of root'); } @@ -729,7 +729,7 @@ module.exports = class FSNodeContext { return this.fs.node(new NodeUIDSelector(parent_uid)); } - async getChild(name) { + async getChild (name) { // If we have a path, we can get an FSNodeContext for the child // without fetching anything. if ( this.path ) { @@ -741,12 +741,12 @@ module.exports = class FSNodeContext { return await this.fs.node(new NodeChildSelector(this.selector, name)); } - - async hasChild(name) { + + async hasChild (name) { return await this.provider.directory_has_name({ parent: this, name }); } - async getTarget() { + async getTarget () { await this.fetchEntry(); const type = await this.get('type'); @@ -763,16 +763,16 @@ module.exports = class FSNodeContext { return this; } - async is_above(child_fsNode) { + async is_above (child_fsNode) { if ( this.isRoot ) return true; const path_this = await this.get('path'); const path_child = await child_fsNode.get('path'); - return path_child.startsWith(path_this + '/'); + return path_child.startsWith(`${path_this }/`); } - async is(fsNode) { + async is (fsNode) { if ( this.mysql_id && fsNode.mysql_id ) { return this.mysql_id === fsNode.mysql_id; } @@ -786,19 +786,19 @@ module.exports = class FSNodeContext { return this.uid === fsNode.uid; } - async getSafeEntry(fetch_options = {}) { + async getSafeEntry (fetch_options = {}) { const svc_event = this.services.get('event'); - + if ( this.found === false ) { - throw new Error(`Tried to get entry of non-existent fsentry: ` + - this.selector.describe(true)); + throw new Error(`Tried to get entry of non-existent fsentry: ${ + this.selector.describe(true)}`); } await this.fetchEntry(fetch_options); const res = this.entry; const fsentry = {}; - if (res.thumbnail) { - await svc_event.emit("thumbnail.read", this.entry); + if ( res.thumbnail ) { + await svc_event.emit('thumbnail.read', this.entry); } // This property will not be serialized, but it can be checked @@ -818,7 +818,7 @@ module.exports = class FSNodeContext { } catch ( _e ) { // fail silently } - if ( ! actor?.type?.user || actor.type.user.id !== res.user_id ) { + if ( !actor?.type?.user || actor.type.user.id !== res.user_id ) { if ( ! fsentry.owner ) await this.fetchOwner(); fsentry.owner = { username: res.owner?.username, @@ -830,10 +830,10 @@ module.exports = class FSNodeContext { const info = this.services.get('information'); - if ( ! this.uid && ! this.entry.uuid ) { - this.log.noticeme('whats even happening!?!? ' + - this.selector.describe() + ' ' + - JSON.stringify(this.entry, null, ' ')); + if ( !this.uid && !this.entry.uuid ) { + this.log.noticeme(`whats even happening!?!? ${ + this.selector.describe() } ${ + JSON.stringify(this.entry, null, ' ')}`); } // If fsentry was found by a path but the entry doesn't @@ -884,9 +884,9 @@ module.exports = class FSNodeContext { } // Add file_request_url - if ( res.file_request_token && res.file_request_token !== '' ){ - fsentry.file_request_url = config.origin + - '/upload?token=' + res.file_request_token; + if ( res.file_request_token && res.file_request_token !== '' ) { + fsentry.file_request_url = `${config.origin + }/upload?token=${ res.file_request_token}`; } if ( fsentry.associated_app_id ) { @@ -900,7 +900,7 @@ module.exports = class FSNodeContext { fsentry.appdata_app = components[2]; } - fsentry.is_dir = !! fsentry.is_dir; + fsentry.is_dir = !!fsentry.is_dir; // Ensure `size` is numeric if ( fsentry.size ) { @@ -910,7 +910,7 @@ module.exports = class FSNodeContext { return fsentry; } - static sanitize_pending_entry_info(res) { + static sanitize_pending_entry_info (res) { const fsentry = {}; // This property will not be serialized, but it can be checked diff --git a/src/backend/src/filesystem/batch/BatchExecutor.js b/src/backend/src/filesystem/batch/BatchExecutor.js index 8309153ccb..748c0cca93 100644 --- a/src/backend/src/filesystem/batch/BatchExecutor.js +++ b/src/backend/src/filesystem/batch/BatchExecutor.js @@ -31,7 +31,7 @@ class BatchExecutor extends AdvancedBase { constructor (x, { actor, log, errors }) { super(); this.x = x; - this.actor = actor + this.actor = actor; this.pathResolver = new PathResolver({ actor }); this.expectations = x.get('services').get('expectations'); this.log = log; @@ -46,7 +46,7 @@ class BatchExecutor extends AdvancedBase { this.concurrent_ops = 0; this.max_concurrent_ops = 20; this.ops_promise = null; - + this.log_batchCommands = (config.logging ?? []).includes('batch-commands'); } @@ -78,7 +78,7 @@ class BatchExecutor extends AdvancedBase { const workUnit = WorkUnit.create(); expectations.expect_eventually({ workUnit, - checkpoint: 'operation responded' + checkpoint: 'operation responded', }); // TEMP: event service will handle this @@ -87,7 +87,7 @@ class BatchExecutor extends AdvancedBase { // run the operation let p = this.x.arun(async () => { - const x= Context.get(); + const x = Context.get(); if ( ! x ) throw new Error('no context'); try { @@ -97,12 +97,12 @@ class BatchExecutor extends AdvancedBase { }); } - if ( file ) workUnit.checkpoint( - 'about to run << ' + - (file.originalname ?? file.name) + - ' >> ' + - JSON.stringify(op) - ); + if ( file ) { + workUnit.checkpoint(`about to run << ${ + file.originalname ?? file.name + } >> ${ + JSON.stringify(op)}`); + } const command_ins = await command_cls.run({ getFile: () => file, pathResolver: this.pathResolver, diff --git a/src/backend/src/filesystem/batch/commands.js b/src/backend/src/filesystem/batch/commands.js index 48a18c80b0..782541d2fe 100644 --- a/src/backend/src/filesystem/batch/commands.js +++ b/src/backend/src/filesystem/batch/commands.js @@ -16,27 +16,26 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { AsyncProviderFeature } = require("../../traits/AsyncProviderFeature"); -const { HLMkdir, QuickMkdir } = require("../hl_operations/hl_mkdir"); -const { Context } = require("../../util/context"); -const { HLWrite } = require("../hl_operations/hl_write"); -const { get_app } = require("../../helpers"); -const { OperationFrame } = require("../../services/OperationTraceService"); -const { HLMkShortcut } = require("../hl_operations/hl_mkshortcut"); -const { HLMkLink } = require("../hl_operations/hl_mklink"); -const { HLRemove } = require("../hl_operations/hl_remove"); - +const { AdvancedBase } = require('@heyputer/putility'); +const { AsyncProviderFeature } = require('../../traits/AsyncProviderFeature'); +const { HLMkdir, QuickMkdir } = require('../hl_operations/hl_mkdir'); +const { Context } = require('../../util/context'); +const { HLWrite } = require('../hl_operations/hl_write'); +const { get_app } = require('../../helpers'); +const { OperationFrame } = require('../../services/OperationTraceService'); +const { HLMkShortcut } = require('../hl_operations/hl_mkshortcut'); +const { HLMkLink } = require('../hl_operations/hl_mklink'); +const { HLRemove } = require('../hl_operations/hl_remove'); class BatchCommand extends AdvancedBase { static FEATURES = [ new AsyncProviderFeature(), - ] + ]; static async run (executor, parameters) { const instance = new this(); let x = Context.get(); const operationTraceSvc = x.get('services').get('operationTrace'); - const frame = await operationTraceSvc.add_frame('batch:' + this.name); + const frame = await operationTraceSvc.add_frame(`batch:${ this.name}`); if ( parameters.hasOwnProperty('item_upload_id') ) { frame.attr('gui_metadata', { ...(frame.get_attr('gui_metadata') || {}), @@ -73,11 +72,9 @@ class MkdirCommand extends BatchCommand { path: parameters.path, }); if ( parameters.as ) { - executor.pathResolver.putSelector( - parameters.as, - q_mkdir.created.selector, - { conflict_free: true } - ); + executor.pathResolver.putSelector(parameters.as, + q_mkdir.created.selector, + { conflict_free: true }); } this.setFactory('result', async () => { await q_mkdir.created.awaitStableEntry(); @@ -101,15 +98,13 @@ class MkdirCommand extends BatchCommand { actor: executor.actor, }); if ( parameters.as ) { - executor.pathResolver.putSelector( - parameters.as, - hl_mkdir.created.selector, - hl_mkdir.used_existing - ? undefined - : { conflict_free: true } - ); + executor.pathResolver.putSelector(parameters.as, + hl_mkdir.created.selector, + hl_mkdir.used_existing + ? undefined + : { conflict_free: true }); } - this.provideValue('result', response) + this.provideValue('result', response); } } @@ -125,7 +120,7 @@ class WriteCommand extends BatchCommand { let app; if ( parameters.app_uid ) { - app = await get_app({uid: parameters.app_uid}) + app = await get_app({ uid: parameters.app_uid }); } const hl_write = new HLWrite(); @@ -158,7 +153,6 @@ class WriteCommand extends BatchCommand { this.provideValue('result', response); - // const opctx = await fs.write(fs, { // // --- per file --- // name: parameters.name, @@ -197,7 +191,7 @@ class ShortcutCommand extends BatchCommand { let app; if ( parameters.app_uid ) { - app = await get_app({uid: parameters.app_uid}) + app = await get_app({ uid: parameters.app_uid }); } await destinationOrParent.fetchEntry({ thumbnail: true }); @@ -232,7 +226,7 @@ class SymlinkCommand extends BatchCommand { let app; if ( parameters.app_uid ) { - app = await get_app({uid: parameters.app_uid}) + app = await get_app({ uid: parameters.app_uid }); } await destinationOrParent.fetchEntry({ thumbnail: true }); @@ -281,5 +275,5 @@ module.exports = { shortcut: ShortcutCommand, symlink: SymlinkCommand, delete: DeleteCommand, - } + }, }; diff --git a/src/backend/src/filesystem/definitions/ts/fsentry.js b/src/backend/src/filesystem/definitions/ts/fsentry.js index 37ca9ff16c..31ff0e64c6 100644 --- a/src/backend/src/filesystem/definitions/ts/fsentry.js +++ b/src/backend/src/filesystem/definitions/ts/fsentry.js @@ -1,10 +1,10 @@ -"use strict"; +'use strict'; // Code generated by protoc-gen-ts_proto. DO NOT EDIT. // versions: // protoc-gen-ts_proto v2.8.0 // protoc v3.21.12 // source: fsentry.proto -Object.defineProperty(exports, "__esModule", { value: true }); +Object.defineProperty(exports, '__esModule', { value: true }); exports.FSEntry = exports.protobufPackage = void 0; /* eslint-disable */ const wire_1 = require("@bufbuild/protobuf/wire"); diff --git a/src/backend/src/filesystem/hl_operations/definitions.js b/src/backend/src/filesystem/hl_operations/definitions.js index 8345cc2778..c566139c29 100644 --- a/src/backend/src/filesystem/hl_operations/definitions.js +++ b/src/backend/src/filesystem/hl_operations/definitions.js @@ -18,8 +18,9 @@ */ const { BaseOperation } = require('../../services/OperationTraceService'); -class HLFilesystemOperation extends BaseOperation {} +class HLFilesystemOperation extends BaseOperation { +} module.exports = { - HLFilesystemOperation + HLFilesystemOperation, }; diff --git a/src/backend/src/filesystem/hl_operations/hl_copy.js b/src/backend/src/filesystem/hl_operations/hl_copy.js index 22d5ad7582..771878c420 100644 --- a/src/backend/src/filesystem/hl_operations/hl_copy.js +++ b/src/backend/src/filesystem/hl_operations/hl_copy.js @@ -16,14 +16,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { chkperm, validate_fsentry_name, get_user, is_ancestor_of } = require("../../helpers"); -const { TYPE_DIRECTORY } = require("../FSNodeContext"); -const { NodePathSelector, RootNodeSelector } = require("../node/selectors"); -const { HLFilesystemOperation } = require("./definitions"); -const { MkTree } = require("./hl_mkdir"); -const { HLRemove } = require("./hl_remove"); -const { LLCopy } = require("../ll_operations/ll_copy"); +const APIError = require('../../api/APIError'); +const { chkperm, validate_fsentry_name, get_user, is_ancestor_of } = require('../../helpers'); +const { TYPE_DIRECTORY } = require('../FSNodeContext'); +const { NodePathSelector, RootNodeSelector } = require('../node/selectors'); +const { HLFilesystemOperation } = require('./definitions'); +const { MkTree } = require('./hl_mkdir'); +const { HLRemove } = require('./hl_remove'); +const { LLCopy } = require('../ll_operations/ll_copy'); class HLCopy extends HLFilesystemOperation { static DESCRIPTION = ` @@ -34,11 +34,11 @@ class HLCopy extends HLFilesystemOperation { - create missing parent directories - overwrite existing files or directories - deduplicate files/directories with the same name - ` + `; static MODULES = { _path: require('path'), - } + }; static PARAMETERS = { source: {}, @@ -51,7 +51,7 @@ class HLCopy extends HLFilesystemOperation { create_missing_parents: {}, user: {}, - } + }; async _run () { const { _path } = this.modules; @@ -84,7 +84,7 @@ class HLCopy extends HLFilesystemOperation { // If parent exists and is a file, and a new name wasn't // specified, the intention must be to overwrite the file. if ( - ! values.new_name && + !values.new_name && await parent.exists() && await parent.get('type') !== TYPE_DIRECTORY ) { @@ -130,20 +130,20 @@ class HLCopy extends HLFilesystemOperation { // NEXT: implement _verify_room with profiling const tracer = svc.get('traceService').tracer; - await tracer.startActiveSpan(`fs:cp:verify-size-constraints`, async span => { + await tracer.startActiveSpan('fs:cp:verify-size-constraints', async span => { const source_file = source.entry; const dest_fsentry = parent.entry; - let source_user = await get_user({id: source_file.user_id}); + let source_user = await get_user({ id: source_file.user_id }); let dest_user = source_user.id !== dest_fsentry.user_id - ? await get_user({id: dest_fsentry.user_id}) + ? await get_user({ id: dest_fsentry.user_id }) : source_user ; const sizeService = svc.get('sizeService'); let deset_usage = await sizeService.get_usage(dest_user.id); const size = await source.fetchSize(); const capacity = await sizeService.get_storage_capacity(dest_user.id); - if(capacity - deset_usage - size < 0){ + if ( capacity - deset_usage - size < 0 ) { throw APIError.create('storage_limit_reached'); } span.end(); @@ -166,16 +166,16 @@ class HLCopy extends HLFilesystemOperation { let overwritten; if ( await dest.exists() ) { // condition: no overwrite behaviour specified - if ( ! values.overwrite && ! values.dedupe_name ) { + if ( !values.overwrite && !values.dedupe_name ) { throw APIError.create('item_with_same_name_exists', null, { - entry_name: dest.entry.name + entry_name: dest.entry.name, }); } if ( values.dedupe_name ) { const target_ext = _path.extname(target_name); const target_noext = _path.basename(target_name, target_ext); - for ( let i=1 ;; i++ ) { + for ( let i = 1 ;; i++ ) { const try_new_name = `${target_noext} (${i})${target_ext}`; const exists = await parent.hasChild(try_new_name); if ( ! exists ) { @@ -209,13 +209,13 @@ class HLCopy extends HLFilesystemOperation { parent, user: values.user, target_name, - }) + }); await this.copied.awaitStableEntry(); const response = await this.copied.getSafeEntry({ thumbnail: true }); return { - copied : response, - overwritten + copied: response, + overwritten, }; } } diff --git a/src/backend/src/filesystem/hl_operations/hl_data_read.js b/src/backend/src/filesystem/hl_operations/hl_data_read.js index 2c66dcd663..1cf5c4688f 100644 --- a/src/backend/src/filesystem/hl_operations/hl_data_read.js +++ b/src/backend/src/filesystem/hl_operations/hl_data_read.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { HLFilesystemOperation } = require("./definitions"); +const { HLFilesystemOperation } = require('./definitions'); const { chkperm } = require('../../helpers'); const { LLRead } = require('../ll_operations/ll_read'); const APIError = require('../../api/APIError'); @@ -29,7 +29,7 @@ const APIError = require('../../api/APIError'); class HLDataRead extends HLFilesystemOperation { static MODULES = { 'stream': require('stream'), - } + }; async _run () { const { context } = this; @@ -53,7 +53,8 @@ class HLDataRead extends HLFilesystemOperation { const ll_read = new LLRead(); let stream = await ll_read.run({ - fsNode, user, + fsNode, + user, version_id, }); @@ -66,8 +67,8 @@ class HLDataRead extends HLFilesystemOperation { _stream_bytes_to_lines (stream) { const readline = require('readline'); const rl = readline.createInterface({ - input: stream, - terminal: false + input: stream, + terminal: false, }); const { PassThrough } = this.modules.stream; @@ -88,7 +89,7 @@ class HLDataRead extends HLFilesystemOperation { const { PassThrough } = this.modules.stream; const output_stream = new PassThrough(); (async () => { - for await (const line of stream) { + for await ( const line of stream ) { output_stream.write(JSON.parse(line)); } output_stream.end(); @@ -98,5 +99,5 @@ class HLDataRead extends HLFilesystemOperation { } module.exports = { - HLDataRead + HLDataRead, }; diff --git a/src/backend/src/filesystem/hl_operations/hl_mkdir.js b/src/backend/src/filesystem/hl_operations/hl_mkdir.js index 94339db7a3..244e4402e3 100644 --- a/src/backend/src/filesystem/hl_operations/hl_mkdir.js +++ b/src/backend/src/filesystem/hl_operations/hl_mkdir.js @@ -18,12 +18,12 @@ */ const { chkperm } = require('../../helpers'); -const { RootNodeSelector, NodeChildSelector, NodePathSelector } = require("../node/selectors"); +const { RootNodeSelector, NodeChildSelector, NodePathSelector } = require('../node/selectors'); const APIError = require('../../api/APIError'); const FSNodeParam = require('../../api/filesystem/FSNodeParam'); const StringParam = require('../../api/filesystem/StringParam'); -const FlagParam = require("../../api/filesystem/FlagParam"); +const FlagParam = require('../../api/filesystem/FlagParam'); const UserParam = require('../../api/filesystem/UserParam'); const FSNodeContext = require('../FSNodeContext'); const { OtelFeature } = require('../../traits/OtelFeature'); @@ -50,16 +50,16 @@ class MkTree extends HLFilesystemOperation { ├── q └── r └── s - ` + `; static PARAMETERS = { parent: new FSNodeParam('parent', { optional: true }), - } + }; static PROPERTIES = { leaves: () => [], directories_created: () => [], - } + }; async _run () { const { values, context } = this; @@ -94,31 +94,33 @@ class MkTree extends HLFilesystemOperation { // This is just a loop that goes through each part of the path // until it finds the first directory that doesn't exist yet. let i = 0; - if ( parent_exists ) for ( ; i < dirs.length ; i++ ) { - const dir = dirs[i]; - const currentParent = current; - current = new NodeChildSelector(current, dir); + if ( parent_exists ) { + for ( ; i < dirs.length ; i++ ) { + const dir = dirs[i]; + const currentParent = current; + current = new NodeChildSelector(current, dir); - const maybe_dir = await fs.node(current); + const maybe_dir = await fs.node(current); - if ( maybe_dir.isRoot ) continue; - if ( await maybe_dir.isUserDirectory() ) continue; + if ( maybe_dir.isRoot ) continue; + if ( await maybe_dir.isUserDirectory() ) continue; - if ( await maybe_dir.exists() ) { + if ( await maybe_dir.exists() ) { - if ( await maybe_dir.get('type') !== FSNodeContext.TYPE_DIRECTORY ) { - throw APIError.create('dest_is_not_a_directory'); + if ( await maybe_dir.get('type') !== FSNodeContext.TYPE_DIRECTORY ) { + throw APIError.create('dest_is_not_a_directory'); + } + + continue; } - continue; + current = currentParent; + parent_exists = false; + break; } - - current = currentParent; - parent_exists = false; - break; } - if ( parent_did_exist && ! parent_exists ) { + if ( parent_did_exist && !parent_exists ) { const node = await fs.node(current); const has_perm = await chkperm(await node.get('entry'), actor.type.user.id, 'write'); if ( ! has_perm ) throw APIError.create('permission_denied'); @@ -145,7 +147,7 @@ class MkTree extends HLFilesystemOperation { parent: await fs.node(currentParent), name: current.name, actor, - }) + }); current = node.selector; @@ -191,9 +193,7 @@ class QuickMkdir extends HLFilesystemOperation { currentSpan.setAttribute('parent', parent.selector.describe()); } - - - for ( let i=0 ; i < dirs.length ; i++ ) { + for ( let i = 0 ; i < dirs.length ; i++ ) { const dir = dirs[i]; const currentParent = current; current = new NodeChildSelector(current, dir); @@ -203,7 +203,7 @@ class QuickMkdir extends HLFilesystemOperation { parent: await fs.node(currentParent), name: current.name, actor, - }) + }); current = node.selector; @@ -224,7 +224,7 @@ class HLMkdir extends HLFilesystemOperation { - overwrite existing files - dedupe names - create shortcuts - ` + `; static PARAMETERS = { parent: new FSNodeParam('parent', { optional: true }), @@ -238,18 +238,18 @@ class HLMkdir extends HLFilesystemOperation { static MODULES = { _path: require('path'), - } + }; static PROPERTIES = { parent_directories_created: () => [], - } + }; static FEATURES = [ new OtelFeature([ '_get_existing_parent', '_create_parents', ]), - ] + ]; async _run () { const { context, values } = this; @@ -290,7 +290,7 @@ class HLMkdir extends HLFilesystemOperation { if ( top_parent.isRoot ) { // root directory is read-only throw APIError.create('forbidden', null, { - message: 'Cannot create directories in the root directory.' + message: 'Cannot create directories in the root directory.', }); } @@ -306,9 +306,7 @@ class HLMkdir extends HLFilesystemOperation { const has_perm = await chkperm(await parent_node.get('entry'), user_id, 'write'); if ( ! has_perm ) throw APIError.create('permission_denied'); - const existing = await fs.node( - new NodeChildSelector(parent_node.selector, target_basename) - ); + const existing = await fs.node(new NodeChildSelector(parent_node.selector, target_basename)); await existing.fetchEntry(); @@ -328,7 +326,7 @@ class HLMkdir extends HLFilesystemOperation { else if ( dedupe_name ) { const fs = context.get('services').get('filesystem'); const parent_selector = parent_node.selector; - for ( let i=1 ;; i++ ) { + for ( let i = 1 ;; i++ ) { let try_new_name = `${target_basename} (${i})`; const selector = new NodeChildSelector(parent_selector, try_new_name); const exists = await parent_node.provider.quick_check({ @@ -411,7 +409,7 @@ class HLMkdir extends HLFilesystemOperation { let remaining_path = _path.dirname(values.path).split('/').filter(Boolean); { const parts = remaining_path.slice(); - for (;;) { + for ( ;; ) { if ( remaining_path.length === 0 ) { return deepest_existing; } @@ -447,7 +445,7 @@ class HLMkdir extends HLFilesystemOperation { : target_dirname.split('/').filter(Boolean); let current = parent_node.selector; - for ( let i=0 ; i < dirs.length ; i++ ) { + for ( let i = 0 ; i < dirs.length ; i++ ) { current = new NodeChildSelector(current, dirs[i]); } diff --git a/src/backend/src/filesystem/hl_operations/hl_mklink.js b/src/backend/src/filesystem/hl_operations/hl_mklink.js index 1e053d93e9..c89b52be9f 100644 --- a/src/backend/src/filesystem/hl_operations/hl_mklink.js +++ b/src/backend/src/filesystem/hl_operations/hl_mklink.js @@ -16,22 +16,22 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const FSNodeParam = require("../../api/filesystem/FSNodeParam"); -const StringParam = require("../../api/filesystem/StringParam"); -const { HLFilesystemOperation } = require("./definitions"); -const APIError = require("../../api/APIError"); -const { TYPE_DIRECTORY } = require("../FSNodeContext"); +const FSNodeParam = require('../../api/filesystem/FSNodeParam'); +const StringParam = require('../../api/filesystem/StringParam'); +const { HLFilesystemOperation } = require('./definitions'); +const APIError = require('../../api/APIError'); +const { TYPE_DIRECTORY } = require('../FSNodeContext'); class HLMkLink extends HLFilesystemOperation { static PARAMETERS = { parent: new FSNodeParam('symlink'), name: new StringParam('name'), target: new StringParam('target'), - } + }; static MODULES = { path: require('node:path'), - } + }; async _run () { const { context, values } = this; diff --git a/src/backend/src/filesystem/hl_operations/hl_mkshortcut.js b/src/backend/src/filesystem/hl_operations/hl_mkshortcut.js index b2d7775c17..73c981191e 100644 --- a/src/backend/src/filesystem/hl_operations/hl_mkshortcut.js +++ b/src/backend/src/filesystem/hl_operations/hl_mkshortcut.js @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const FSNodeParam = require("../../api/filesystem/FSNodeParam"); -const FlagParam = require("../../api/filesystem/FlagParam"); -const StringParam = require("../../api/filesystem/StringParam"); -const { TYPE_DIRECTORY } = require("../FSNodeContext"); -const { HLFilesystemOperation } = require("./definitions"); +const APIError = require('../../api/APIError'); +const FSNodeParam = require('../../api/filesystem/FSNodeParam'); +const FlagParam = require('../../api/filesystem/FlagParam'); +const StringParam = require('../../api/filesystem/StringParam'); +const { TYPE_DIRECTORY } = require('../FSNodeContext'); +const { HLFilesystemOperation } = require('./definitions'); class HLMkShortcut extends HLFilesystemOperation { static PARAMETERS = { @@ -30,14 +30,14 @@ class HLMkShortcut extends HLFilesystemOperation { target: new FSNodeParam('target'), dedupe_name: new FlagParam('dedupe_name', { optional: true }), - } + }; static MODULES = { path: require('node:path'), - } + }; async _run () { - console.log('HLMKSHORTCUT IS HAPPENING') + console.log('HLMKSHORTCUT IS HAPPENING'); const { context, values } = this; const fs = context.get('services').get('filesystem'); @@ -50,7 +50,7 @@ class HLMkShortcut extends HLFilesystemOperation { if ( ! name ) { dedupe_name = true; - name = 'Shortcut to ' + await target.get('name'); + name = `Shortcut to ${ await target.get('name')}`; } { @@ -79,7 +79,7 @@ class HLMkShortcut extends HLFilesystemOperation { const name_ext = this.modules.path.extname(name); const name_noext = this.modules.path.basename(name, name_ext); - for ( let i=1 ;; i++ ) { + for ( let i = 1 ;; i++ ) { const try_new_name = `${name_noext} (${i})${name_ext}`; const try_dest = await parent.getChild(try_new_name); if ( ! await try_dest.exists() ) { diff --git a/src/backend/src/filesystem/hl_operations/hl_name_search.js b/src/backend/src/filesystem/hl_operations/hl_name_search.js index f9a61f36f8..9430134fa8 100644 --- a/src/backend/src/filesystem/hl_operations/hl_name_search.js +++ b/src/backend/src/filesystem/hl_operations/hl_name_search.js @@ -1,26 +1,26 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { DB_READ } = require("../../services/database/consts"); -const { Context } = require("../../util/context"); -const { NodeUIDSelector } = require("../node/selectors"); -const { HLFilesystemOperation } = require("./definitions"); +const { DB_READ } = require('../../services/database/consts'); +const { Context } = require('../../util/context'); +const { NodeUIDSelector } = require('../node/selectors'); +const { HLFilesystemOperation } = require('./definitions'); class HLNameSearch extends HLFilesystemOperation { async _run () { @@ -31,20 +31,18 @@ class HLNameSearch extends HLFilesystemOperation { .get(DB_READ, 'fs.namesearch'); term = term.replace(/%/g, ''); - term = '%' + term + '%'; - + term = `%${ term }%`; + // Only user actors can do this, because the permission // system would otherwise slow things down if ( ! actor.type.user ) return []; - const results = await db.read( - `SELECT uuid FROM fsentries WHERE name LIKE ? AND ` + - `user_id = ? LIMIT 50`, - [term, actor.type.user.id] - ); - + const results = await db.read('SELECT uuid FROM fsentries WHERE name LIKE ? AND ' + + 'user_id = ? LIMIT 50', + [term, actor.type.user.id]); + const uuids = results.map(v => v.uuid); - + const fsnodes = await Promise.all(uuids.map(async uuid => { return await svc_fs.node(new NodeUIDSelector(uuid)); })); diff --git a/src/backend/src/filesystem/hl_operations/hl_read.js b/src/backend/src/filesystem/hl_operations/hl_read.js index 9c5bbd3e7c..27cedc16a1 100644 --- a/src/backend/src/filesystem/hl_operations/hl_read.js +++ b/src/backend/src/filesystem/hl_operations/hl_read.js @@ -16,22 +16,22 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { LLRead } = require("../ll_operations/ll_read"); -const { HLFilesystemOperation } = require("./definitions"); +const APIError = require('../../api/APIError'); +const { LLRead } = require('../ll_operations/ll_read'); +const { HLFilesystemOperation } = require('./definitions'); class HLRead extends HLFilesystemOperation { static CONCERN = 'filesystem'; static MODULES = { 'stream': require('stream'), - } + }; async _run () { const { fsNode, actor, line_count, byte_count, offset, - version_id, range + version_id, range, } = this.values; if ( ! await fsNode.exists() ) { @@ -40,12 +40,13 @@ class HLRead extends HLFilesystemOperation { const ll_read = new LLRead(); let stream = await ll_read.run({ - fsNode, actor, + fsNode, + actor, version_id, range, ...(byte_count !== undefined ? { offset: offset ?? 0, - length: byte_count + length: byte_count, } : {}), }); @@ -64,8 +65,8 @@ class HLRead extends HLFilesystemOperation { _wrap_stream_line_count (stream, line_count) { const readline = require('readline'); const rl = readline.createInterface({ - input: stream, - terminal: false + input: stream, + terminal: false, }); const { PassThrough } = this.modules.stream; @@ -75,11 +76,11 @@ class HLRead extends HLFilesystemOperation { let lines_read = 0; new Promise((resolve, reject) => { rl.on('line', (line) => { - if(lines_read++ >= line_count){ + if ( lines_read++ >= line_count ) { return rl.close(); } - output_stream.write(lines_read > 1 ? '\r\n' + line : line); + output_stream.write(lines_read > 1 ? `\r\n${ line}` : line); }); rl.on('error', () => { console.log('error'); @@ -94,5 +95,5 @@ class HLRead extends HLFilesystemOperation { } module.exports = { - HLRead + HLRead, }; diff --git a/src/backend/src/filesystem/hl_operations/hl_readdir.js b/src/backend/src/filesystem/hl_operations/hl_readdir.js index 1a1ce687d9..9b37b00ad8 100644 --- a/src/backend/src/filesystem/hl_operations/hl_readdir.js +++ b/src/backend/src/filesystem/hl_operations/hl_readdir.js @@ -16,19 +16,19 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { Context } = require("../../util/context"); -const { stream_to_buffer } = require("../../util/streamutil"); -const { ECMAP } = require("../ECMAP"); -const { TYPE_DIRECTORY, TYPE_SYMLINK } = require("../FSNodeContext"); -const { LLListUsers } = require("../ll_operations/ll_listusers"); -const { LLReadDir } = require("../ll_operations/ll_readdir"); -const { LLReadShares } = require("../ll_operations/ll_readshares"); -const { HLFilesystemOperation } = require("./definitions"); +const APIError = require('../../api/APIError'); +const { Context } = require('../../util/context'); +const { stream_to_buffer } = require('../../util/streamutil'); +const { ECMAP } = require('../ECMAP'); +const { TYPE_DIRECTORY, TYPE_SYMLINK } = require('../FSNodeContext'); +const { LLListUsers } = require('../ll_operations/ll_listusers'); +const { LLReadDir } = require('../ll_operations/ll_readdir'); +const { LLReadShares } = require('../ll_operations/ll_readshares'); +const { HLFilesystemOperation } = require('./definitions'); class HLReadDir extends HLFilesystemOperation { static CONCERN = 'filesystem'; - async _run() { + async _run () { return ECMAP.arun(async () => { const ecmap = Context.get(ECMAP.SYMBOL); ecmap.store_fsNodeContext(this.values.subject); @@ -61,15 +61,14 @@ class HLReadDir extends HLFilesystemOperation { } throw APIError.create('readdir_of_non_directory'); } - + let children; this.log.debug('READDIR', - { - userdir: await subject.isUserDirectory(), - namediff: await subject.get('name') !== user.username - } - ); + { + userdir: await subject.isUserDirectory(), + namediff: await subject.get('name') !== user.username, + }); if ( subject.isRoot ) { const ll_listusers = new LLListUsers(); children = await ll_listusers.run(this.values); @@ -100,7 +99,7 @@ class HLReadDir extends HLFilesystemOperation { ]); } const entry = await child.getSafeEntry(); - if ( ! no_thumbs && entry.associated_app ) { + if ( !no_thumbs && entry.associated_app ) { const svc_appIcon = this.context.get('services').get('app-icon'); const icon_result = await svc_appIcon.get_icon_stream({ app_icon: entry.associated_app.icon, diff --git a/src/backend/src/filesystem/hl_operations/hl_remove.js b/src/backend/src/filesystem/hl_operations/hl_remove.js index 9e76c193ea..b4e6c8c955 100644 --- a/src/backend/src/filesystem/hl_operations/hl_remove.js +++ b/src/backend/src/filesystem/hl_operations/hl_remove.js @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { chkperm } = require("../../helpers"); -const { TYPE_DIRECTORY } = require("../FSNodeContext"); -const { LLRmDir } = require("../ll_operations/ll_rmdir"); -const { LLRmNode } = require("../ll_operations/ll_rmnode"); -const { HLFilesystemOperation } = require("./definitions"); +const APIError = require('../../api/APIError'); +const { chkperm } = require('../../helpers'); +const { TYPE_DIRECTORY } = require('../FSNodeContext'); +const { LLRmDir } = require('../ll_operations/ll_rmdir'); +const { LLRmNode } = require('../ll_operations/ll_rmnode'); +const { HLFilesystemOperation } = require('./definitions'); class HLRemove extends HLFilesystemOperation { static PARAMETERS = { @@ -29,7 +29,7 @@ class HLRemove extends HLFilesystemOperation { user: {}, recursive: {}, descendants_only: {}, - } + }; async _run () { const { target, user } = this.values; diff --git a/src/backend/src/filesystem/hl_operations/hl_stat.js b/src/backend/src/filesystem/hl_operations/hl_stat.js index 499221bc95..034555ac78 100644 --- a/src/backend/src/filesystem/hl_operations/hl_stat.js +++ b/src/backend/src/filesystem/hl_operations/hl_stat.js @@ -16,16 +16,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { Context } = require("../../util/context"); -const { HLFilesystemOperation } = require("./definitions"); +const { Context } = require('../../util/context'); +const { HLFilesystemOperation } = require('./definitions'); const APIError = require('../../api/APIError'); -const { ECMAP } = require("../ECMAP"); -const { NodeUIDSelector } = require("../node/selectors"); +const { ECMAP } = require('../ECMAP'); +const { NodeUIDSelector } = require('../node/selectors'); class HLStat extends HLFilesystemOperation { static MODULES = { ['mime-types']: require('mime-types'), - } + }; async _run () { return await ECMAP.arun(async () => { @@ -46,12 +46,12 @@ class HLStat extends HLFilesystemOperation { return_versions, return_size, } = this.values; - + const maybe_uid_selector = subject.get_selector_of_type(NodeUIDSelector); - + // users created before 2025-07-30 might have fsentries with NULL paths. // we can remove this check once that is fixed. - const user_unix_ts = Number((''+Date.parse(Context.get('actor')?.type?.user?.timestamp)).slice(0, -3)); + const user_unix_ts = Number((`${Date.parse(Context.get('actor')?.type?.user?.timestamp)}`).slice(0, -3)); const paths_are_fine = user_unix_ts >= 1722385593; if ( maybe_uid_selector || paths_are_fine ) { @@ -67,7 +67,7 @@ class HLStat extends HLFilesystemOperation { } // file not found - if( ! subject.found ) throw APIError.create('subject_does_not_exist'); + if ( ! subject.found ) throw APIError.create('subject_does_not_exist'); await subject.fetchOwner(); @@ -80,20 +80,20 @@ class HLStat extends HLFilesystemOperation { // TODO: why is this specific to stat? const mime = this.require('mime-types'); - const contentType = mime.contentType(subject.entry.name) + const contentType = mime.contentType(subject.entry.name); subject.entry.type = contentType ? contentType : null; - if (return_size) await subject.fetchSize(user); - if (return_subdomains) await subject.fetchSubdomains(user) - if (return_shares || return_permissions) { + if ( return_size ) await subject.fetchSize(user); + if ( return_subdomains ) await subject.fetchSubdomains(user); + if ( return_shares || return_permissions ) { await subject.fetchShares(); } - if (return_versions) await subject.fetchVersions(); + if ( return_versions ) await subject.fetchVersions(); return await subject.getSafeEntry(); } } module.exports = { - HLStat + HLStat, }; diff --git a/src/backend/src/filesystem/lib/PuterPath.js b/src/backend/src/filesystem/lib/PuterPath.js index 7ed91fcc96..2a2457ab10 100644 --- a/src/backend/src/filesystem/lib/PuterPath.js +++ b/src/backend/src/filesystem/lib/PuterPath.js @@ -20,10 +20,10 @@ const _path = require('path'); /** * Puter paths look like any of the following: - * + * * Absolute path: /user/dir1/dir2/file * From UID: AAAA-BBBB-CCCC-DDDD/../a/b/c - * + * * The difference between an absolute path and a UID-relative path * is the leading forward-slash character. */ @@ -44,9 +44,11 @@ class PuterPath { this.normUnix = _path.normalize(text); this.normFlat = (this.normUnix.endsWith('/') && this.normUnix.length > 1) - ? this.normUnix.slice(0, -1) : this.normUnix; + ? this.normUnix.slice(0, -1) : this.normUnix; + } + get text () { + return this.text_; } - get text () { return this.text_; } isRoot () { if ( this.normFlat === '/' ) return true; @@ -61,7 +63,7 @@ class PuterPath { } isFromUID () { - return ! this.isAbsolute(); + return !this.isAbsolute(); } get reference () { diff --git a/src/backend/src/filesystem/ll_operations/definitions.js b/src/backend/src/filesystem/ll_operations/definitions.js index 7b41d93a40..1cd7ad7552 100644 --- a/src/backend/src/filesystem/ll_operations/definitions.js +++ b/src/backend/src/filesystem/ll_operations/definitions.js @@ -16,10 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { BaseOperation } = require("../../services/OperationTraceService"); +const { BaseOperation } = require('../../services/OperationTraceService'); -class LLFilesystemOperation extends BaseOperation {} +class LLFilesystemOperation extends BaseOperation { +} module.exports = { - LLFilesystemOperation + LLFilesystemOperation, }; diff --git a/src/backend/src/filesystem/ll_operations/ll_copy.js b/src/backend/src/filesystem/ll_operations/ll_copy.js index ff5297383b..b81c16d2d5 100644 --- a/src/backend/src/filesystem/ll_operations/ll_copy.js +++ b/src/backend/src/filesystem/ll_operations/ll_copy.js @@ -23,7 +23,7 @@ class LLCopy extends LLFilesystemOperation { static MODULES = { _path: require('path'), uuidv4: require('uuid').v4, - } + }; async _run () { const { _path, uuidv4 } = this.modules; @@ -36,7 +36,7 @@ class LLCopy extends LLFilesystemOperation { const svc_event = svc.get('event'); const uuid = uuidv4(); - const ts = Math.round(Date.now()/1000); + const ts = Math.round(Date.now() / 1000); this.field('target-uid', uuid); this.field('source', source.selector.describe()); diff --git a/src/backend/src/filesystem/ll_operations/ll_listusers.js b/src/backend/src/filesystem/ll_operations/ll_listusers.js index 9df809e072..25a10d21f3 100644 --- a/src/backend/src/filesystem/ll_operations/ll_listusers.js +++ b/src/backend/src/filesystem/ll_operations/ll_listusers.js @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { RootNodeSelector, NodeChildSelector } = require("../node/selectors"); -const { LLFilesystemOperation } = require("./definitions"); +const { RootNodeSelector, NodeChildSelector } = require('../node/selectors'); +const { LLFilesystemOperation } = require('./definitions'); class LLListUsers extends LLFilesystemOperation { static description = ` List user directories which are relevant to the current actor. `; - + async _run () { const { context } = this; const svc = context.get('services'); @@ -35,19 +35,16 @@ class LLListUsers extends LLFilesystemOperation { const issuers = await svc_permission.list_user_permission_issuers(user); const nodes = []; - - nodes.push(await svc_fs.node(new NodeChildSelector( - new RootNodeSelector(), - user.username, - ))); + + nodes.push(await svc_fs.node(new NodeChildSelector(new RootNodeSelector(), + user.username))); for ( const issuer of issuers ) { - const node = await svc_fs.node(new NodeChildSelector( - new RootNodeSelector(), - issuer.username)); + const node = await svc_fs.node(new NodeChildSelector(new RootNodeSelector(), + issuer.username)); nodes.push(node); } - + return nodes; } } diff --git a/src/backend/src/filesystem/ll_operations/ll_move.js b/src/backend/src/filesystem/ll_operations/ll_move.js index 24fe70996b..d1812e958a 100644 --- a/src/backend/src/filesystem/ll_operations/ll_move.js +++ b/src/backend/src/filesystem/ll_operations/ll_move.js @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { LLFilesystemOperation } = require("./definitions"); +const { LLFilesystemOperation } = require('./definitions'); class LLMove extends LLFilesystemOperation { static MODULES = { _path: require('path'), - } + }; async _run () { const { context } = this; diff --git a/src/backend/src/filesystem/ll_operations/ll_read.js b/src/backend/src/filesystem/ll_operations/ll_read.js index 2aedd638f4..84cec3a93b 100644 --- a/src/backend/src/filesystem/ll_operations/ll_read.js +++ b/src/backend/src/filesystem/ll_operations/ll_read.js @@ -31,7 +31,7 @@ const checkACLForRead = async (aclService, actor, fsNode, skip = false) => { if ( skip ) { return; } - if ( !await aclService.check(actor, fsNode, 'read') ) { + if ( ! await aclService.check(actor, fsNode, 'read') ) { throw await aclService.get_safe_acl_error(actor, fsNode, 'read'); } }; @@ -43,7 +43,7 @@ const typeCheckForRead = async (fsNode) => { class LLRead extends LLFilesystemOperation { static CONCERN = 'filesystem'; - async _run({ fsNode, no_acl, actor, offset, length, range, version_id } = {}){ + async _run ({ fsNode, no_acl, actor, offset, length, range, version_id } = {}) { // extract services from context const aclService = Context.get('services').get('acl'); const db = Context.get('services') @@ -51,7 +51,7 @@ class LLRead extends LLFilesystemOperation { const fileCacheService = Context.get('services').get('file-cache'); // validate input - if ( !await fsNode.exists() ){ + if ( ! await fsNode.exists() ) { throw APIError.create('subject_does_not_exist'); } // validate initial node @@ -86,7 +86,7 @@ class LLRead extends LLFilesystemOperation { [Date.now() / 1000, await fsNode.get('mysql-id')]); const ownerId = await fsNode.get('user_id'); - const chargedActor = actor? actor: new Actor({ + const chargedActor = actor ? actor : new Actor({ type: new UserActorType({ user: await get_user({ id: ownerId }), }), @@ -141,7 +141,7 @@ class LLRead extends LLFilesystemOperation { // Meter ingress const size = await (async () => { - if ( range ){ + if ( range ) { const match = range.match(/bytes=(\d+)-(\d+)/); if ( match ) { const start = parseInt(match[1], 10); @@ -157,7 +157,7 @@ class LLRead extends LLFilesystemOperation { meteringService.incrementUsage(chargedActor, 'filesystem:egress:bytes', size); // cache if whole file read - if ( !has_range ) { + if ( ! has_range ) { // only cache for non-memoryfs providers if ( ! (fsNode.provider instanceof MemoryFSProvider) ) { const res = await fileCacheService.maybe_store(fsNode, stream); diff --git a/src/backend/src/filesystem/ll_operations/ll_readdir.js b/src/backend/src/filesystem/ll_operations/ll_readdir.js index ea0d689920..428a230676 100644 --- a/src/backend/src/filesystem/ll_operations/ll_readdir.js +++ b/src/backend/src/filesystem/ll_operations/ll_readdir.js @@ -16,17 +16,17 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const fsCapabilities = require("../definitions/capabilities"); -const { ECMAP } = require("../ECMAP"); -const { TYPE_SYMLINK } = require("../FSNodeContext"); -const { RootNodeSelector } = require("../node/selectors"); -const { NodeUIDSelector, NodeChildSelector } = require("../node/selectors"); -const { LLFilesystemOperation } = require("./definitions"); +const APIError = require('../../api/APIError'); +const fsCapabilities = require('../definitions/capabilities'); +const { ECMAP } = require('../ECMAP'); +const { TYPE_SYMLINK } = require('../FSNodeContext'); +const { RootNodeSelector } = require('../node/selectors'); +const { NodeUIDSelector, NodeChildSelector } = require('../node/selectors'); +const { LLFilesystemOperation } = require('./definitions'); class LLReadDir extends LLFilesystemOperation { static CONCERN = 'filesystem'; - async _run() { + async _run () { return ECMAP.arun(async () => { return await this.__run(); }); @@ -65,10 +65,8 @@ class LLReadDir extends LLFilesystemOperation { if ( subject.isRoot ) { if ( ! actor.type.user ) return []; return [ - await svc_fs.node(new NodeChildSelector( - new RootNodeSelector(), - actor.type.user.username, - )) + await svc_fs.node(new NodeChildSelector(new RootNodeSelector(), + actor.type.user.username)), ]; } @@ -76,12 +74,12 @@ class LLReadDir extends LLFilesystemOperation { // UUID Mode if ( capabilities.has(fsCapabilities.READDIR_UUID_MODE) ) { - this.checkpoint('readdir uuid mode') + this.checkpoint('readdir uuid mode'); const child_uuids = await subject.provider.readdir({ context, node: subject, }); - this.checkpoint('after get direct descendants') + this.checkpoint('after get direct descendants'); const children = await Promise.all(child_uuids.map(async uuid => { return await svc_fs.node(new NodeUIDSelector(uuid)); })); diff --git a/src/backend/src/filesystem/ll_operations/ll_readshares.js b/src/backend/src/filesystem/ll_operations/ll_readshares.js index 4803f4b1e8..ad0d759bf4 100644 --- a/src/backend/src/filesystem/ll_operations/ll_readshares.js +++ b/src/backend/src/filesystem/ll_operations/ll_readshares.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { get_user } = require("../../helpers"); -const { MANAGE_PERM_PREFIX } = require("../../services/auth/permissionConts.mjs"); -const { PermissionUtil } = require("../../services/auth/permissionUtils.mjs"); -const { DB_WRITE } = require("../../services/database/consts"); -const { NodeUIDSelector } = require("../node/selectors"); -const { LLFilesystemOperation } = require("./definitions"); -const { LLReadDir } = require("./ll_readdir"); +const { get_user } = require('../../helpers'); +const { MANAGE_PERM_PREFIX } = require('../../services/auth/permissionConts.mjs'); +const { PermissionUtil } = require('../../services/auth/permissionUtils.mjs'); +const { DB_WRITE } = require('../../services/database/consts'); +const { NodeUIDSelector } = require('../node/selectors'); +const { LLFilesystemOperation } = require('./definitions'); +const { LLReadDir } = require('./ll_readdir'); class LLReadShares extends LLFilesystemOperation { static description = ` @@ -34,7 +34,7 @@ class LLReadShares extends LLFilesystemOperation { will not be traversed. `; - async _run() { + async _run () { const { subject, user, actor } = this.values; const svc = this.context.get('services'); diff --git a/src/backend/src/filesystem/ll_operations/ll_rmdir.js b/src/backend/src/filesystem/ll_operations/ll_rmdir.js index e8c7674b6e..c8b0bcb48f 100644 --- a/src/backend/src/filesystem/ll_operations/ll_rmdir.js +++ b/src/backend/src/filesystem/ll_operations/ll_rmdir.js @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { MemoryFSProvider } = require("../../modules/puterfs/customfs/MemoryFSProvider"); -const { ParallelTasks } = require("../../util/otelutil"); -const FSNodeContext = require("../FSNodeContext"); -const { NodeUIDSelector } = require("../node/selectors"); -const { LLFilesystemOperation } = require("./definitions"); +const APIError = require('../../api/APIError'); +const { MemoryFSProvider } = require('../../modules/puterfs/customfs/MemoryFSProvider'); +const { ParallelTasks } = require('../../util/otelutil'); +const FSNodeContext = require('../FSNodeContext'); +const { NodeUIDSelector } = require('../node/selectors'); +const { LLFilesystemOperation } = require('./definitions'); const { LLRmNode } = require('./ll_rmnode'); class LLRmDir extends LLFilesystemOperation { @@ -32,7 +32,7 @@ class LLRmDir extends LLFilesystemOperation { actor, descendants_only, recursive, - + // internal use only - not for clients ignore_not_empty, @@ -54,7 +54,7 @@ class LLRmDir extends LLFilesystemOperation { } } - if ( await target.get('immutable') && ! descendants_only ) { + if ( await target.get('immutable') && !descendants_only ) { throw APIError.create('immutable'); } @@ -64,7 +64,7 @@ class LLRmDir extends LLFilesystemOperation { node: target, }); - if ( children.length > 0 && ! recursive && ! ignore_not_empty ) { + if ( children.length > 0 && !recursive && !ignore_not_empty ) { throw APIError.create('not_empty'); } @@ -72,17 +72,13 @@ class LLRmDir extends LLFilesystemOperation { const tasks = new ParallelTasks({ tracer, max: max_tasks }); for ( const child_uuid of children ) { - tasks.add(`fs:rm:rm-child`, async () => { - const child_node = await fs.node( - new NodeUIDSelector(child_uuid) - ); + tasks.add('fs:rm:rm-child', async () => { + const child_node = await fs.node(new NodeUIDSelector(child_uuid)); const type = await child_node.get('type'); if ( type === FSNodeContext.TYPE_DIRECTORY ) { const ll_rm = new LLRmDir(); await ll_rm.run({ - target: await fs.node( - new NodeUIDSelector(child_uuid), - ), + target: await fs.node(new NodeUIDSelector(child_uuid)), user, recursive: true, descendants_only: false, @@ -92,9 +88,7 @@ class LLRmDir extends LLFilesystemOperation { } else { const ll_rm = new LLRmNode(); await ll_rm.run({ - target: await fs.node( - new NodeUIDSelector(child_uuid), - ), + target: await fs.node(new NodeUIDSelector(child_uuid)), user, }); } @@ -105,23 +99,23 @@ class LLRmDir extends LLFilesystemOperation { // TODO (xiaochen): consolidate these two branches if ( target.provider instanceof MemoryFSProvider ) { - await target.provider.rmdir( { + await target.provider.rmdir({ context, node: target, options: { recursive, descendants_only, }, - } ); + }); } else { if ( ! descendants_only ) { - await target.provider.rmdir( { + await target.provider.rmdir({ context, node: target, options: { ignore_not_empty: true, }, - } ); + }); } } } diff --git a/src/backend/src/filesystem/ll_operations/ll_rmnode.js b/src/backend/src/filesystem/ll_operations/ll_rmnode.js index 50618c66ae..c2c6417c28 100644 --- a/src/backend/src/filesystem/ll_operations/ll_rmnode.js +++ b/src/backend/src/filesystem/ll_operations/ll_rmnode.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { LLFilesystemOperation } = require("./definitions"); +const { LLFilesystemOperation } = require('./definitions'); class LLRmNode extends LLFilesystemOperation { async _run () { @@ -24,7 +24,7 @@ class LLRmNode extends LLFilesystemOperation { const { context } = this; - const svc_event = context.get('services').get("event"); + const svc_event = context.get('services').get('event'); // Access Control { diff --git a/src/backend/src/filesystem/ll_operations/ll_write.js b/src/backend/src/filesystem/ll_operations/ll_write.js index b566dd4ff0..05a734498e 100644 --- a/src/backend/src/filesystem/ll_operations/ll_write.js +++ b/src/backend/src/filesystem/ll_operations/ll_write.js @@ -16,14 +16,14 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { LLFilesystemOperation } = require("./definitions"); -const APIError = require("../../api/APIError"); +const { LLFilesystemOperation } = require('./definitions'); +const APIError = require('../../api/APIError'); /** * The "overwrite" write operation. - * + * * This operation is used to write a file to an existing path. - * + * * @extends LLFilesystemOperation */ class LLOWrite extends LLFilesystemOperation { @@ -59,9 +59,9 @@ class LLOWrite extends LLFilesystemOperation { /** * The "non-overwrite" write operation. - * + * * This operation is used to write a file to a non-existent path. - * + * * @extends LLFilesystemOperation */ class LLCWrite extends LLFilesystemOperation { @@ -69,7 +69,7 @@ class LLCWrite extends LLFilesystemOperation { _path: require('path'), uuidv4: require('uuid').v4, config: require('../../config.js'), - } + }; /** * Executes the create operation by writing a new file to the parent directory. diff --git a/src/backend/src/filesystem/node/selectors.js b/src/backend/src/filesystem/node/selectors.js index 76e5200b16..79c2eab989 100644 --- a/src/backend/src/filesystem/node/selectors.js +++ b/src/backend/src/filesystem/node/selectors.js @@ -75,7 +75,7 @@ class NodeUIDSelector extends NodeSelector { class NodeInternalIDSelector extends NodeSelector { constructor (service, id, debugInfo) { - super(); + super(); this.service = service; this.id = id; this.debugInfo = debugInfo; @@ -91,9 +91,9 @@ class NodeInternalIDSelector extends NodeSelector { if ( showDebug ) { return `[db:${this.id}] (${ JSON.stringify(this.debugInfo, null, 2) - })` + })`; } - return `[db:${this.id}]` + return `[db:${this.id}]`; } } @@ -114,7 +114,7 @@ class NodeChildSelector extends NodeSelector { } describe () { - return this.parent.describe() + '/' + this.name; + return `${this.parent.describe() }/${ this.name}`; } } @@ -144,7 +144,7 @@ class NodeRawEntrySelector extends NodeSelector { constructor (entry) { super(); // Fix entries from get_descendants - if ( ! entry.uuid && entry.uid ) { + if ( !entry.uuid && entry.uid ) { entry.uuid = entry.uid; if ( entry._id ) { entry.id = entry._id; @@ -206,7 +206,7 @@ const relativeSelector = (parent, path) => { } return selector; -} +}; module.exports = { NodeSelector, diff --git a/src/backend/src/filesystem/node/states.js b/src/backend/src/filesystem/node/states.js index 5c350a2dc7..10822c5e8d 100644 --- a/src/backend/src/filesystem/node/states.js +++ b/src/backend/src/filesystem/node/states.js @@ -16,8 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -class NodeFoundState {} +class NodeFoundState { +} -class NodeDoesNotExistState {} +class NodeDoesNotExistState { +} -class NodeInitialState {} +class NodeInitialState { +} diff --git a/src/backend/src/filesystem/storage/UploadProgressTracker.js b/src/backend/src/filesystem/storage/UploadProgressTracker.js index 5f49645883..8ecbc0a443 100644 --- a/src/backend/src/filesystem/storage/UploadProgressTracker.js +++ b/src/backend/src/filesystem/storage/UploadProgressTracker.js @@ -67,8 +67,8 @@ class UploadProgressTracker { if ( idx !== -1 ) { listeners.splice(idx, 1); } - } - } + }, + }; return det; } diff --git a/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js b/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js index e05bcb6c65..208ea79696 100644 --- a/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js +++ b/src/backend/src/filesystem/strategies/storage_a/LocalDiskStorageStrategy.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { BaseOperation } = require("../../../services/OperationTraceService"); +const { BaseOperation } = require('../../../services/OperationTraceService'); /** * Handles file upload operations to local disk storage. @@ -58,7 +58,7 @@ class LocalDiskUploadStrategy extends BaseOperation { on_progress: evt => { progress_tracker.set_total(file.size); progress_tracker.set(evt.uploaded); - } + }, }); } } @@ -66,7 +66,8 @@ class LocalDiskUploadStrategy extends BaseOperation { /** * Hook called after the operation is inserted into the trace. */ - post_insert () {} + post_insert () { + } } /** @@ -105,7 +106,8 @@ class LocalDiskCopyStrategy extends BaseOperation { /** * Hook called after the operation is inserted into the trace. */ - post_insert () {} + post_insert () { + } } /** diff --git a/src/backend/src/filesystem/validation.js b/src/backend/src/filesystem/validation.js index c35bd251f8..00e8e51cb1 100644 --- a/src/backend/src/filesystem/validation.js +++ b/src/backend/src/filesystem/validation.js @@ -24,7 +24,7 @@ This module contains functions that validate filesystem operations. /* eslint-disable no-control-regex */ -const config = require("../config"); +const config = require('../config'); const path_excludes = () => /[\x00-\x1F]/g; const node_excludes = () => /[/\x00-\x1F]/g; @@ -38,8 +38,8 @@ const safety_excludes = [ /[\u2066-\u2069]/, // RTL and LTR isolate /[\u2028-\u2029]/, // line and paragraph separator /[\uFF01-\uFF5E]/, // fullwidth ASCII - /[\u2060]/, // word joiner - /[\uFEFF]/, // zero width no-break space + /[\u2060]/, // word joiner + /[\uFEFF]/, // zero width no-break space /[\uFFFE-\uFFFF]/, // non-characters ]; @@ -56,7 +56,7 @@ const is_valid_node_name = function is_valid_node_name (name) { if ( name_without_dots.length < 1 ) return false; return true; -} +}; const is_valid_path = function is_valid_path (path, { no_relative_components, @@ -69,8 +69,10 @@ const is_valid_path = function is_valid_path (path, { if ( exclude.test(path) ) return false; } - if ( ! allow_path_fragment ) if ( path[0] !== '/' && path[0] !== '.' ) { - return false; + if ( ! allow_path_fragment ) { + if ( path[0] !== '/' && path[0] !== '.' ) { + return false; + } } if ( no_relative_components ) { @@ -83,7 +85,7 @@ const is_valid_path = function is_valid_path (path, { } return true; -} +}; module.exports = { is_valid_node_name, diff --git a/src/backend/src/fun/dev-console-ui-utils.js b/src/backend/src/fun/dev-console-ui-utils.js index d2e3fd57b3..1c45357674 100644 --- a/src/backend/src/fun/dev-console-ui-utils.js +++ b/src/backend/src/fun/dev-console-ui-utils.js @@ -32,7 +32,7 @@ const surrounding_box = (col, lines, lengths) => { if ( ! lengths ) { lengths = lines.map(line => stringLength(line)); } - + const probably_docker = (() => { try { // I don't know what the value of this is in Docker, diff --git a/src/backend/src/fun/logos.js b/src/backend/src/fun/logos.js index abd6dfb641..520e81bf9a 100644 --- a/src/backend/src/fun/logos.js +++ b/src/backend/src/fun/logos.js @@ -17,9 +17,9 @@ * along with this program. If not, see . */ module.exports = [ -{ -sz:40, -txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& + { + sz: 40, + txt: `&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&. ,& &&&&&&&&&&& @@ -34,11 +34,11 @@ txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&& .&&&&& & &&&&& &&&&&&&& &&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& -&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&` -}, -{ -sz:72, -txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& +&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`, + }, + { + sz: 72, + txt: `&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& @@ -67,5 +67,5 @@ txt:`&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&`, -} + }, ]; diff --git a/src/backend/src/helpers.js b/src/backend/src/helpers.js index 1c7def7175..15297301b2 100644 --- a/src/backend/src/helpers.js +++ b/src/backend/src/helpers.js @@ -18,7 +18,7 @@ */ const _path = require('path'); const micromatch = require('micromatch'); -const config = require('./config') +const config = require('./config'); const mime = require('mime-types'); const { ManagedError } = require('./util/errorutil.js'); const { spanify } = require('./util/otelutil.js'); @@ -33,31 +33,27 @@ let services = null; const tmp_provide_services = async ss => { services = ss; await services.ready; -} +}; -async function is_empty(dir_uuid){ +async function is_empty (dir_uuid) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); - + let rows; if ( typeof dir_uuid === 'object' ) { if ( typeof dir_uuid.path === 'string' && dir_uuid.path !== '' ) { - rows = await db.read( - `SELECT EXISTS(SELECT 1 FROM fsentries WHERE path LIKE ${db.case({ - sqlite: `? || '%'`, - otherwise: `CONCAT(?, '%')`, - })} LIMIT 1) AS not_empty`, - [dir_uuid.path + '/'] - ); + rows = await db.read(`SELECT EXISTS(SELECT 1 FROM fsentries WHERE path LIKE ${db.case({ + sqlite: '? || \'%\'', + otherwise: 'CONCAT(?, \'%\')', + })} LIMIT 1) AS not_empty`, + [`${dir_uuid.path }/`]); } else dir_uuid = dir_uuid.uid; } - + if ( typeof dir_uuid === 'string' ) { - rows = await db.read( - `SELECT EXISTS(SELECT 1 FROM fsentries WHERE parent_uid = ? LIMIT 1) AS not_empty`, - [dir_uuid] - ); + rows = await db.read('SELECT EXISTS(SELECT 1 FROM fsentries WHERE parent_uid = ? LIMIT 1) AS not_empty', + [dir_uuid]); } return !rows[0].not_empty; @@ -66,7 +62,7 @@ async function is_empty(dir_uuid){ /** * @deprecated - sharing will be implemented with user-to-user ACL */ -async function has_shared_with(user_id, recipient_user_id){ +async function has_shared_with (user_id, recipient_user_id) { return false; } @@ -78,7 +74,7 @@ async function has_shared_with(user_id, recipient_user_id){ * * @deprecated - sharing will be implemented with user-to-user ACL */ -async function is_shared_with(fsentry_id, recipient_user_id){ +async function is_shared_with (fsentry_id, recipient_user_id) { return false; } @@ -87,10 +83,10 @@ async function is_shared_with(fsentry_id, recipient_user_id){ * * @param {*} fsentry_id * @param {*} recipient_user_id - * + * * @deprecated - sharing will be implemented with user-to-user ACL */ - async function is_shared_with_anyone(fsentry_id){ +async function is_shared_with_anyone (fsentry_id) { return false; } @@ -98,24 +94,26 @@ async function is_shared_with(fsentry_id, recipient_user_id){ * Checks to see if temp_users is disabled and return a boolean * @returns {boolean} */ -async function is_temp_users_disabled() { - const svc_feature_flag = await services.get("feature-flag"); - return await svc_feature_flag.check("temp-users-disabled"); +async function is_temp_users_disabled () { + const svc_feature_flag = await services.get('feature-flag'); + return await svc_feature_flag.check('temp-users-disabled'); } /** * Checks to see if user_signup is disabled and return a boolean * @returns {boolean} */ -async function is_user_signup_disabled() { - const svc_feature_flag = await services.get("feature-flag"); - return await svc_feature_flag.check("user-signup-disabled"); +async function is_user_signup_disabled () { + const svc_feature_flag = await services.get('feature-flag'); + return await svc_feature_flag.check('user-signup-disabled'); } const chkperm = spanify('chkperm', async (target_fsentry, requester_user_id, action) => { // basic cases where false is the default response - if(!target_fsentry) + if ( ! target_fsentry ) + { return false; + } // pseudo-entry from FSNodeContext if ( target_fsentry.is_root ) { @@ -123,18 +121,22 @@ const chkperm = spanify('chkperm', async (target_fsentry, requester_user_id, act } // requester is the owner of this entry - if(target_fsentry.user_id === requester_user_id){ + if ( target_fsentry.user_id === requester_user_id ) { return true; } // this entry was shared with the requester - else if(await is_shared_with(target_fsentry.id, requester_user_id)){ + else if ( await is_shared_with(target_fsentry.id, requester_user_id) ) { return true; } // special case: owner of entry has shared at least one entry with requester and requester is asking for the owner's root directory: /[owner_username] - else if(target_fsentry.parent_uid === null && await has_shared_with(target_fsentry.user_id, requester_user_id) && action !== 'write') + else if ( target_fsentry.parent_uid === null && await has_shared_with(target_fsentry.user_id, requester_user_id) && action !== 'write' ) + { return true; + } else + { return false; + } }); /** @@ -143,21 +145,35 @@ const chkperm = spanify('chkperm', async (target_fsentry, requester_user_id, act * @param {string} name * @returns */ -function validate_fsentry_name(name){ - if(!name) - throw {message: 'Name can not be empty.'} - else if(!isString(name)) - throw {message: "Name can only be a string."} - else if(name.includes('/')) - throw {message: "Name can not contain the '/' character."} - else if(name === '.') - throw {message: "Name can not be the '.' character."}; - else if(name === '..') - throw {message: "Name can not be the '..' character."}; - else if(name.length > config.max_fsentry_name_length) - throw {message: `Name can not be longer than ${config.max_fsentry_name_length} characters`} +function validate_fsentry_name (name) { + if ( ! name ) + { + throw { message: 'Name can not be empty.' }; + } + else if ( ! isString(name) ) + { + throw { message: 'Name can only be a string.' }; + } + else if ( name.includes('/') ) + { + throw { message: "Name can not contain the '/' character." }; + } + else if ( name === '.' ) + { + throw { message: "Name can not be the '.' character." }; + } + else if ( name === '..' ) + { + throw { message: "Name can not be the '..' character." }; + } + else if ( name.length > config.max_fsentry_name_length ) + { + throw { message: `Name can not be longer than ${config.max_fsentry_name_length} characters` }; + } else - return true + { + return true; + } } /** @@ -166,16 +182,20 @@ function validate_fsentry_name(name){ * @param {integer} id - `id` of FSEntry * @returns {Promise} Promise object represents the UUID of the FileSystem Entry */ -async function id2uuid(id){ +async function id2uuid (id) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); - let fsentry = await db.requireRead("SELECT `uuid`, immutable FROM `fsentries` WHERE `id` = ? LIMIT 1", [id]); + let fsentry = await db.requireRead('SELECT `uuid`, immutable FROM `fsentries` WHERE `id` = ? LIMIT 1', [id]); - if(!fsentry[0]) + if ( ! fsentry[0] ) + { return null; + } else + { return fsentry[0].uuid; + } } /** @@ -184,15 +204,19 @@ async function id2uuid(id){ * @param {integer} user_id - `user_id` of user * @returns {Promise} Promise object represents the UUID of the FileSystem Entry */ - async function df(user_id){ +async function df (user_id) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); - const fsentry = await db.read("SELECT SUM(size) AS total FROM `fsentries` WHERE `user_id` = ? LIMIT 1", [user_id]); - if(!fsentry[0] || !fsentry[0].total) + const fsentry = await db.read('SELECT SUM(size) AS total FROM `fsentries` WHERE `user_id` = ? LIMIT 1', [user_id]); + if ( !fsentry[0] || !fsentry[0].total ) + { return 0; + } else + { return fsentry[0].total; + } } /** @@ -204,7 +228,7 @@ async function id2uuid(id){ * @param {string} options - `options` * @returns {Promise} */ -async function get_user(options) { +async function get_user (options) { return await services.get('get-user').get_user(options); } @@ -214,10 +238,10 @@ async function get_user(options) { * @param {User} userID - the user entry to invalidate */ function invalidate_cached_user (user) { - kv.del('users:username:' + user.username); - kv.del('users:uuid:' + user.uuid); - kv.del('users:email:' + user.email); - kv.del('users:id:' + user.id); + kv.del(`users:username:${ user.username}`); + kv.del(`users:uuid:${ user.uuid}`); + kv.del(`users:email:${ user.email}`); + kv.del(`users:id:${ user.id}`); } /** @@ -225,7 +249,7 @@ function invalidate_cached_user (user) { * @param {number} id - the id of the user to invalidate */ function invalidate_cached_user_by_id (id) { - const user = kv.get('users:id:' + id); + const user = kv.get(`users:id:${ id}`); if ( ! user ) return; invalidate_cached_user(user); } @@ -236,7 +260,7 @@ function invalidate_cached_user_by_id (id) { * @param {string} options - `options` * @returns {Promise} */ -async function refresh_apps_cache(options, override){ +async function refresh_apps_cache (options, override) { return; /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'apps'); @@ -245,47 +269,53 @@ async function refresh_apps_cache(options, override){ const log = services.get('log-service').create('refresh_apps_cache'); log.tick('refresh apps cache'); // if options is not provided, refresh all apps - if(!options){ + if ( ! options ) { let apps = await db.read('SELECT * FROM apps'); - for (let index = 0; index < apps.length; index++) { + for ( let index = 0; index < apps.length; index++ ) { const app = apps[index]; - kv.set('apps:name:' + app.name, app); - kv.set('apps:id:' + app.id, app); - kv.set('apps:uid:' + app.uid, app); + kv.set(`apps:name:${ app.name}`, app); + kv.set(`apps:id:${ app.id}`, app); + kv.set(`apps:uid:${ app.uid}`, app); } svc_event.emit('apps.invalidate', { options, apps, }); } // refresh only apps that are approved for listing - else if(options.only_approved_for_listing){ + else if ( options.only_approved_for_listing ) { let apps = await db.read('SELECT * FROM apps WHERE approved_for_listing = 1'); - for (let index = 0; index < apps.length; index++) { + for ( let index = 0; index < apps.length; index++ ) { const app = apps[index]; - kv.set('apps:name:' + app.name, app); - kv.set('apps:id:' + app.id, app); - kv.set('apps:uid:' + app.uid, app); + kv.set(`apps:name:${ app.name}`, app); + kv.set(`apps:id:${ app.id}`, app); + kv.set(`apps:uid:${ app.uid}`, app); } svc_event.emit('apps.invalidate', { options, apps, }); } // if options is provided, refresh only the app specified - else{ + else { let app; - if(options.name) + if ( options.name ) + { app = await db.pread('SELECT * FROM apps WHERE name = ?', [options.name]); - else if(options.uid) + } + else if ( options.uid ) + { app = await db.pread('SELECT * FROM apps WHERE uid = ?', [options.uid]); - else if(options.id) + } + else if ( options.id ) + { app = await db.pread('SELECT * FROM apps WHERE id = ?', [options.id]); + } else { log.error('invalid options to refresh_apps_cache'); throw new Error('Invalid options provided'); } - if(!app || !app[0]) { + if ( !app || !app[0] ) { log.error('refresh_apps_cache could not find the app'); return; } else { @@ -293,9 +323,9 @@ async function refresh_apps_cache(options, override){ if ( override ) { Object.assign(app, override); } - kv.set('apps:name:' + app.name, app); - kv.set('apps:id:' + app.id, app); - kv.set('apps:uid:' + app.uid, app); + kv.set(`apps:name:${ app.name}`, app); + kv.set(`apps:id:${ app.id}`, app); + kv.set(`apps:uid:${ app.uid}`, app); } svc_event.emit('apps.invalidate', { @@ -304,7 +334,7 @@ async function refresh_apps_cache(options, override){ } } -async function refresh_associations_cache(){ +async function refresh_associations_cache () { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'apps'); @@ -333,7 +363,7 @@ async function refresh_associations_cache(){ * @param {string} options - `options` * @returns {Promise} */ - async function get_app(options){ +async function get_app (options) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'apps'); @@ -341,7 +371,7 @@ async function refresh_associations_cache(){ let app = []; // This condition should be updated if the code below is re-ordered. - if ( options.follow_old_names && ! options.uid && options.name ) { + if ( options.follow_old_names && !options.uid && options.name ) { const svc_oldAppName = services.get('old-app-name'); const old_name = await svc_oldAppName.check_app_name(options.name); if ( old_name ) { @@ -353,36 +383,36 @@ async function refresh_associations_cache(){ } } - if(options.uid){ + if ( options.uid ) { // try cache first app[0] = kv.get(`apps:uid:${options.uid}`); // not in cache, try db - if(!app[0]) { - log.cache(false, 'apps:uid:' + options.uid); - app = await db.read("SELECT * FROM `apps` WHERE `uid` = ? LIMIT 1", [options.uid]); + if ( ! app[0] ) { + log.cache(false, `apps:uid:${ options.uid}`); + app = await db.read('SELECT * FROM `apps` WHERE `uid` = ? LIMIT 1', [options.uid]); } - }else if(options.name){ + } else if ( options.name ) { // try cache first app[0] = kv.get(`apps:name:${options.name}`); // not in cache, try db - if(!app[0]) { - log.cache(false, 'apps:name:' + options.name); - app = await db.read("SELECT * FROM `apps` WHERE `name` = ? LIMIT 1", [options.name]); + if ( ! app[0] ) { + log.cache(false, `apps:name:${ options.name}`); + app = await db.read('SELECT * FROM `apps` WHERE `name` = ? LIMIT 1', [options.name]); } } - else if(options.id){ + else if ( options.id ) { // try cache first app[0] = kv.get(`apps:id:${options.id}`); // not in cache, try db - if(!app[0]) { - log.cache(false, 'apps:id:' + options.id); - app = await db.read("SELECT * FROM `apps` WHERE `id` = ? LIMIT 1", [options.id]); + if ( ! app[0] ) { + log.cache(false, `apps:id:${ options.id}`); + app = await db.read('SELECT * FROM `apps` WHERE `id` = ? LIMIT 1', [options.id]); } } app = app && app[0] ? app[0] : null; if ( app === null ) return null; - + // kv.set(`apps:uid:${app.uid}`, app, { EX: 30 }); // kv.set(`apps:name:${app.name}`, app, { EX: 30 }); // kv.set(`apps:id:${app.id}`, app, { EX: 30 }); @@ -399,41 +429,45 @@ async function refresh_associations_cache(){ * @param {string} options - `options` * @returns {Promise} */ - async function app_exists(options){ +async function app_exists (options) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'apps'); let app; - if(options.uid) - app = await db.read("SELECT `id` FROM `apps` WHERE `uid` = ? LIMIT 1", [options.uid]); - else if(options.name) - app = await db.read("SELECT `id` FROM `apps` WHERE `name` = ? LIMIT 1", [options.name]); - else if(options.id) - app = await db.read("SELECT `id` FROM `apps` WHERE `id` = ? LIMIT 1", [options.id]); + if ( options.uid ) + { + app = await db.read('SELECT `id` FROM `apps` WHERE `uid` = ? LIMIT 1', [options.uid]); + } + else if ( options.name ) + { + app = await db.read('SELECT `id` FROM `apps` WHERE `name` = ? LIMIT 1', [options.name]); + } + else if ( options.id ) + { + app = await db.read('SELECT `id` FROM `apps` WHERE `id` = ? LIMIT 1', [options.id]); + } return app[0]; } - /** * change username * * @param {string} options - `options` * @returns {Promise} */ - async function change_username(user_id, new_username){ +async function change_username (user_id, new_username) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_WRITE, 'auth'); - const old_username = (await get_user({id: user_id})).username; + const old_username = (await get_user({ id: user_id })).username; // update username - await db.write("UPDATE `user` SET username = ? WHERE `id` = ? LIMIT 1", [new_username, user_id]); + await db.write('UPDATE `user` SET username = ? WHERE `id` = ? LIMIT 1', [new_username, user_id]); // update root directory name for this user - await db.write("UPDATE `fsentries` SET `name` = ?, `path` = ? " + - "WHERE `user_id` = ? AND parent_uid IS NULL LIMIT 1", - [new_username, '/' + new_username, user_id] - ); + await db.write('UPDATE `fsentries` SET `name` = ?, `path` = ? ' + + 'WHERE `user_id` = ? AND parent_uid IS NULL LIMIT 1', + [new_username, `/${ new_username}`, user_id]); const log = services.get('log-service').create('change_username'); log.noticeme(`User ${old_username} changed username to ${new_username}`); @@ -442,7 +476,6 @@ async function refresh_associations_cache(){ invalidate_cached_user_by_id(user_id); } - /** * Find a FSEntry by its uuid * @@ -450,14 +483,13 @@ async function refresh_associations_cache(){ * @returns {Promise} Promise object represents the UUID of the FileSystem Entry * @deprecated Use fs middleware instead */ -async function uuid2fsentry(uuid, return_thumbnail){ +async function uuid2fsentry (uuid, return_thumbnail) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); // todo optim, check if uuid is not exactly 36 characters long, if not it's invalid // and we can avoid one unnecessary DB lookup - let fsentry = await db.requireRead( - `SELECT + let fsentry = await db.requireRead(`SELECT id, associated_app_id, uuid, @@ -481,13 +513,16 @@ async function uuid2fsentry(uuid, return_thumbnail){ accessed, size FROM fsentries WHERE uuid = ? LIMIT 1`, - [uuid] - ); + [uuid]); - if(!fsentry[0]) + if ( ! fsentry[0] ) + { return false; + } else + { return fsentry[0]; + } } /** @@ -496,14 +531,13 @@ async function uuid2fsentry(uuid, return_thumbnail){ * @param {integer} id - `id` of FSEntry * @returns {Promise} Promise object represents the UUID of the FileSystem Entry */ - async function id2fsentry(id, return_thumbnail){ +async function id2fsentry (id, return_thumbnail) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); // todo optim, check if uuid is not exactly 36 characters long, if not it's invalid // and we can avoid one unnecessary DB lookup - let fsentry = await db.requireRead( - `SELECT + let fsentry = await db.requireRead(`SELECT id, uuid, public_token, @@ -525,13 +559,14 @@ async function uuid2fsentry(uuid, return_thumbnail){ accessed, size FROM fsentries WHERE id = ? LIMIT 1`, - [id] - ); + [id]); - if(!fsentry[0]){ + if ( ! fsentry[0] ) { return false; - }else + } else + { return fsentry[0]; + } } /** @@ -542,72 +577,76 @@ async function uuid2fsentry(uuid, return_thumbnail){ * @returns {false|object} - `false` if path could not be resolved, otherwise an object representing the FSEntry * @deprecated Use fs middleware instead */ -async function convert_path_to_fsentry(path){ - // todo optim, check if path is valid (e.g. contaisn valid characters) - // if syntactical errors are found we can potentially avoid some expensive db lookups - - // '/' means that parent_uid is null - // TODO: facade fsentry for root (devlog:2023-06-01) - if(path === '/') - return null; - //first slash is redundant - path = path.substr(path.indexOf('/') + 1) - //last slash, if existing is redundant - if(path[path.length - 1] === '/') - path = path.slice(0, -1); - //split path into parts - const fsentry_names = path.split('/'); - - // if no parts, return false - if(fsentry_names.length === 0) - return false; +async function convert_path_to_fsentry (path) { + // todo optim, check if path is valid (e.g. contaisn valid characters) + // if syntactical errors are found we can potentially avoid some expensive db lookups + + // '/' means that parent_uid is null + // TODO: facade fsentry for root (devlog:2023-06-01) + if ( path === '/' ) + { + return null; + } + //first slash is redundant + path = path.substr(path.indexOf('/') + 1); + //last slash, if existing is redundant + if ( path[path.length - 1] === '/' ) + { + path = path.slice(0, -1); + } + //split path into parts + const fsentry_names = path.split('/'); - let parent_uid = null; - let final_res = null; - let is_public = false - let result + // if no parts, return false + if ( fsentry_names.length === 0 ) + { + return false; + } - /** @type BaseDatabaseAccessService */ - const db = services.get('database').get(DB_READ, 'filesystem'); + let parent_uid = null; + let final_res = null; + let is_public = false; + let result; - // Try stored path first - result = await db.read( - `SELECT * FROM fsentries WHERE path=? LIMIT 1`, - ['/' + path], - ); + /** @type BaseDatabaseAccessService */ + const db = services.get('database').get(DB_READ, 'filesystem'); - if ( result[0] ) { - return result[0]; + // Try stored path first + result = await db.read('SELECT * FROM fsentries WHERE path=? LIMIT 1', + [`/${ path}`]); + + if ( result[0] ) { + return result[0]; + } + + for ( let i = 0; i < fsentry_names.length; i++ ) { + if ( parent_uid === null ) { + result = await db.read('SELECT * FROM fsentries WHERE parent_uid IS NULL AND name=? LIMIT 1', + [fsentry_names[i]]); + } + else { + result = await db.read('SELECT * FROM fsentries WHERE parent_uid = ? AND name=? LIMIT 1', + [parent_uid, fsentry_names[i]]); } - for(let i=0; i < fsentry_names.length; i++){ - if(parent_uid === null){ - result = await db.read( - `SELECT * FROM fsentries WHERE parent_uid IS NULL AND name=? LIMIT 1`, - [fsentry_names[i]] - ); + if ( result[0] ) { + parent_uid = result[0].uuid; + // is_public is either directly specified or inherited from parent dir + if ( result[0].is_public === null ) + { + result[0].is_public = is_public; } - else{ - result = await db.read( - `SELECT * FROM fsentries WHERE parent_uid = ? AND name=? LIMIT 1`, - [parent_uid, fsentry_names[i]] - ); + else + { + is_public = result[0].is_public; } - if(result[0] ){ - parent_uid = result[0].uuid; - // is_public is either directly specified or inherited from parent dir - if(result[0].is_public === null) - result[0].is_public = is_public - else - is_public = result[0].is_public - - }else{ - return false; - } - final_res = result + } else { + return false; } - return final_res[0]; + final_res = result; + } + return final_res[0]; } /** @@ -615,27 +654,27 @@ async function convert_path_to_fsentry(path){ * @param {integer} bytes - size in bytes * @returns {string} bytes in human-readable format */ -function byte_format(bytes){ +function byte_format (bytes) { // calculate and return bytes in human-readable format const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; - if (typeof bytes !== "number" || bytes < 1) { + if ( typeof bytes !== 'number' || bytes < 1 ) { return '0 B'; } const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); - return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; + return `${Math.round(bytes / Math.pow(1024, i), 2) } ${ sizes[i]}`; }; -const get_dir_size = async (path, user)=>{ +const get_dir_size = async (path, user) => { let size = 0; const descendants = await get_descendants(path, user); - for(let i=0; i < descendants.length; i++){ - if(!descendants[i].is_dir){ + for ( let i = 0; i < descendants.length; i++ ) { + if ( ! descendants[i].is_dir ) { size += descendants[i].size; } } return size; -} +}; /** * Recursively retrieve all files, directories, and subdirectories under `path`. @@ -653,7 +692,7 @@ const get_descendants_0 = async (path, user, depth, return_thumbnail = false) => // decrement depth if it's set depth !== undefined && depth--; // turn path into absolute form - path = _path.resolve('/', path) + path = _path.resolve('/', path); // get parent dir const parent = await convert_path_to_fsentry(path); // holds array that will be returned @@ -664,9 +703,10 @@ const get_descendants_0 = async (path, user, depth, return_thumbnail = false) => // try to extract username from path let username; let split_path = path.split('/'); - if(split_path.length === 2 && split_path[0] === '') + if ( split_path.length === 2 && split_path[0] === '' ) + { username = split_path[1]; - + } /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); @@ -674,29 +714,25 @@ const get_descendants_0 = async (path, user, depth, return_thumbnail = false) => // ------------------------------------- // parent is root ('/') // ------------------------------------- - if(parent === null){ + if ( parent === null ) { path = ''; // direct children under root - children = await db.read( - `SELECT + children = await db.read(`SELECT id, uuid, parent_uid, name, metadata, is_dir, bucket, bucket_region, modified, created, immutable, shortcut_to, is_shortcut, sort_by, associated_app_id, ${return_thumbnail ? 'thumbnail, ' : ''} accessed, size FROM fsentries WHERE user_id = ? AND parent_uid IS NULL`, - [user.id] - ); + [user.id]); // users that have shared files/dirs with this user - const sharing_users = await db.read( - `SELECT DISTINCT(owner_user_id), user.username + const sharing_users = await db.read(`SELECT DISTINCT(owner_user_id), user.username FROM share INNER JOIN user ON user.id = share.owner_user_id WHERE share.recipient_user_id = ?`, - [user.id] - ); - if(sharing_users.length>0){ - for(let i=0; i 0 ) { + for ( let i = 0; i < sharing_users.length; i++ ) { let dir = {}; dir.id = null; dir.uuid = null; @@ -704,30 +740,31 @@ const get_descendants_0 = async (path, user, depth, return_thumbnail = false) => dir.name = sharing_users[i].username; dir.is_dir = true; dir.immutable = true; - children.push(dir) + children.push(dir); } } } // ------------------------------------- // parent doesn't exist // ------------------------------------- - else if(parent === false){ + else if ( parent === false ) { return []; } // ------------------------------------- // Parent is a shared-user directory: /[some_username](/) // but make sure `[some_username]` is not the same as the requester's username // ------------------------------------- - else if(username && username !== user.username){ + else if ( username && username !== user.username ) { children = []; let sharing_user; - sharing_user = await get_user({username: username}); - if(!sharing_user) + sharing_user = await get_user({ username: username }); + if ( ! sharing_user ) + { return []; + } // shared files/dirs with this user - const shared_fsentries = await db.read( - `SELECT + const shared_fsentries = await db.read(`SELECT fsentries.id, fsentries.user_id, fsentries.uuid, fsentries.parent_uid, fsentries.bucket, fsentries.bucket_region, fsentries.name, fsentries.shortcut_to, fsentries.is_shortcut, fsentries.metadata, fsentries.is_dir, fsentries.modified, fsentries.created, fsentries.accessed, fsentries.size, fsentries.sort_by, fsentries.associated_app_id, @@ -736,37 +773,36 @@ const get_descendants_0 = async (path, user, depth, return_thumbnail = false) => FROM share INNER JOIN fsentries ON fsentries.id = share.fsentry_id WHERE share.recipient_user_id = ? AND owner_user_id = ?`, - [user.id, sharing_user.id] - ); + [user.id, sharing_user.id]); // merge `children` and `shared_fsentries` - if(shared_fsentries.length>0){ - for(let i=0; i 0 ) { + for ( let i = 0; i < shared_fsentries.length; i++ ) { shared_fsentries[i].path = await id2path(shared_fsentries[i].id); - children.push(shared_fsentries[i]) + children.push(shared_fsentries[i]); } } } // ------------------------------------- // All other cases // ------------------------------------- - else{ + else { children = []; - let temp_children = await db.read( - `SELECT + let temp_children = await db.read(`SELECT id, user_id, uuid, parent_uid, name, metadata, is_shortcut, shortcut_to, is_dir, modified, created, accessed, size, sort_by, associated_app_id, is_symlink, symlink_path, immutable ${return_thumbnail ? ', thumbnail' : ''} FROM fsentries WHERE parent_uid = ?`, - [parent.uuid] - ); + [parent.uuid]); // check if user has access to each file, if yes add it - if(temp_children.length>0){ - for(let i=0; i 0 ) { + for ( let i = 0; i < temp_children.length; i++ ) { const tchild = temp_children[i]; - if(await chkperm(tchild, user.id)) + if ( await chkperm(tchild, user.id) ) + { children.push(tchild); + } } } } @@ -777,58 +813,57 @@ const get_descendants_0 = async (path, user, depth, return_thumbnail = false) => const ids = children.map(child => child.id); const qmarks = ids.map(() => '?').join(','); - let rows = await db.read( - `SELECT root_dir_id FROM subdomains WHERE root_dir_id IN (${qmarks}) AND user_id=?`, - [...ids, user.id]); + let rows = await db.read(`SELECT root_dir_id FROM subdomains WHERE root_dir_id IN (${qmarks}) AND user_id=?`, + [...ids, user.id]); log.debug('rows???', rows); const websiteMap = {}; for ( const row of rows ) websiteMap[row.root_dir_id] = true; - for(let i=0; i 0)) - ){ - ret.push(await get_descendants(path + '/' + children[i].name, user, depth)) + ) { + ret.push(await get_descendants(`${path }/${ children[i].name}`, user, depth)); } } return ret.flat(); -} +}; const get_descendants = async (...args) => { const tracer = services.get('traceService').tracer; @@ -838,14 +873,14 @@ const get_descendants = async (...args) => { span.end(); }); return ret; -} +}; /** * * @param {integer} entry_id * @returns */ - const id2path = async (entry_uid)=>{ +const id2path = async (entry_uid) => { if ( entry_uid == null ) { throw new Error('got null or undefined entry id'); } @@ -861,12 +896,12 @@ const get_descendants = async (...args) => { let result; - return await traces.spanify(`helpers:id2path`, async () => { - log.debug(`entry id: ${entry_uid}`) + return await traces.spanify('helpers:id2path', async () => { + log.debug(`entry id: ${entry_uid}`); if ( typeof entry_uid === 'number' ) { const old = entry_uid; entry_uid = await id2uuid(entry_uid); - log.debug(`entry id resolved: resolved ${old} ${entry_uid}`) + log.debug(`entry id resolved: resolved ${old} ${entry_uid}`); } try { @@ -880,8 +915,8 @@ const get_descendants = async (...args) => { SELECT e.uuid, e.parent_uid, e.name, ${ db.case({ - sqlite: `e.name || '/' || cte.path`, - otherwise: `CONCAT(e.name, '/', cte.path)`, + sqlite: 'e.name || \'/\' || cte.path', + otherwise: 'CONCAT(e.name, \'/\', cte.path)', }) } FROM fsentries e @@ -898,25 +933,25 @@ const get_descendants = async (...args) => { message: `error while resolving path for ${entry_uid}: ${e.message}`, extra: { entry_uid, - } + }, }); throw new ManagedError(`cannot create path for ${entry_uid}`); } - if ( ! result || ! result[0] ) { + if ( !result || !result[0] ) { errors.report('id2path.select', { alarm: true, message: `no result for ${entry_uid}`, extra: { entry_uid, - } + }, }); throw new ManagedError(`cannot create path for ${entry_uid}`); } - return '/' + result[0].path; - }) -} + return `/${ result[0].path}`; + }); +}; /** * @@ -924,28 +959,28 @@ const get_descendants = async (...args) => { * @param {object} user * @returns */ -async function resolve_glob(glob, user){ +async function resolve_glob (glob, user) { //turn glob into abs path - glob = _path.resolve('/', glob) + glob = _path.resolve('/', glob); //get base of glob - const base = micromatch.scan(glob).base + const base = micromatch.scan(glob).base; //estimate needed depth - let depth = 1 - const dirs = glob.split('/') - for(let i=0; i< dirs.length; i++){ - if(dirs[i].includes('**')){ - depth = undefined - break - }else{ - depth++ + let depth = 1; + const dirs = glob.split('/'); + for ( let i = 0; i < dirs.length; i++ ) { + if ( dirs[i].includes('**') ) { + depth = undefined; + break; + } else { + depth++; } } - const descendants = await get_descendants(base, user, depth) + const descendants = await get_descendants(base, user, depth); return descendants.filter((fsentry) => { - return fsentry.path && micromatch.isMatch(fsentry.path, glob) - }) + return fsentry.path && micromatch.isMatch(fsentry.path, glob); + }); } /** @@ -956,16 +991,16 @@ async function resolve_glob(glob, user){ * @param {object} user * @returns */ -function cp(source_path, dest_path, user, overwrite, change_name, check_perms = true){ - throw new Error(`legacy copy function called`); +function cp (source_path, dest_path, user, overwrite, change_name, check_perms = true) { + throw new Error('legacy copy function called'); } -function isString(variable) { +function isString (variable) { return typeof variable === 'string' || variable instanceof String; } // checks to see if given variable is an object -function isObject(variable) { +function isObject (variable) { return variable !== null && typeof variable === 'object'; } @@ -976,26 +1011,26 @@ function isObject(variable) { * @param {object} user * @returns */ -function rm(source_path, user, descendants_only = false){ - throw new Error(`legacy remove function called`); +function rm (source_path, user, descendants_only = false) { + throw new Error('legacy remove function called'); } const body_parser_error_handler = (err, req, res, next) => { - if (err instanceof SyntaxError && err.status === 400 && 'body' in err) { + if ( err instanceof SyntaxError && err.status === 400 && 'body' in err ) { return res.status(400).send(err); // Bad request } next(); -} +}; /** * Given a uid, returns a file node. - * + * * TODO (xiaochen): It only works for MemoryFSProvider currently. - * + * * @param {string} uid - The uid of the file to get. * @returns {Promise} The file node, or null if the file does not exist. */ -async function get_entry(uid) { +async function get_entry (uid) { const svc_mountpoint = Context.get('services').get('mountpoint'); const uid_selector = new NodeUIDSelector(uid); const provider = await svc_mountpoint.get_provider(uid_selector); @@ -1010,7 +1045,7 @@ async function get_entry(uid) { }); } -async function is_ancestor_of(ancestor_uid, descendant_uid){ +async function is_ancestor_of (ancestor_uid, descendant_uid) { const ancestor = await get_entry(ancestor_uid); const descendant = await get_entry(descendant_uid); @@ -1018,16 +1053,19 @@ async function is_ancestor_of(ancestor_uid, descendant_uid){ return descendant.path.startsWith(ancestor.path); } - /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); // root is an ancestor to all FSEntries - if(ancestor_uid === null) + if ( ancestor_uid === null ) + { return true; + } // root is never a descendant to any FSEntries - if(descendant_uid === null) + if ( descendant_uid === null ) + { return false; + } if ( typeof ancestor_uid === 'number' ) { ancestor_uid = await id2uuid(ancestor_uid); @@ -1036,20 +1074,22 @@ async function is_ancestor_of(ancestor_uid, descendant_uid){ descendant_uid = await id2uuid(descendant_uid); } - let parent = await db.read("SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1", [descendant_uid]); - if(parent[0] === undefined) - parent = await db.pread("SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1", [descendant_uid]); - if(parent[0].uuid === ancestor_uid || parent[0].parent_uid === ancestor_uid){ + let parent = await db.read('SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1', [descendant_uid]); + if ( parent[0] === undefined ) + { + parent = await db.pread('SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1', [descendant_uid]); + } + if ( parent[0].uuid === ancestor_uid || parent[0].parent_uid === ancestor_uid ) { return true; } // keep checking as long as parent of parent is not root - while(parent[0].parent_uid !== null){ - parent = await db.read("SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1", [parent[0].parent_uid]); - if(parent[0] === undefined) { - parent = await db.pread("SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1", [descendant_uid]); + while ( parent[0].parent_uid !== null ) { + parent = await db.read('SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1', [parent[0].parent_uid]); + if ( parent[0] === undefined ) { + parent = await db.pread('SELECT `uuid`, `parent_uid` FROM `fsentries` WHERE `uuid` = ? LIMIT 1', [descendant_uid]); } - if(parent[0].uuid === ancestor_uid || parent[0].parent_uid === ancestor_uid){ + if ( parent[0].uuid === ancestor_uid || parent[0].parent_uid === ancestor_uid ) { return true; } } @@ -1057,12 +1097,12 @@ async function is_ancestor_of(ancestor_uid, descendant_uid){ return false; } -async function sign_file(fsentry, action){ +async function sign_file (fsentry, action) { const sha256 = require('js-sha256').sha256; // fsentry not found - if(fsentry === false){ - throw {message: 'No entry found with this uid'}; + if ( fsentry === false ) { + throw { message: 'No entry found with this uid' }; } const uid = fsentry.uuid ?? (fsentry.uid ?? fsentry._id); @@ -1082,24 +1122,24 @@ async function sign_file(fsentry, action){ write_url: `${config.api_base_url}/writeFile?uid=${uid}&expires=${expires}&signature=${signature}`, metadata_url: `${config.api_base_url}/itemMetadata?uid=${uid}&expires=${expires}&signature=${signature}`, fsentry_type: contentType, - fsentry_is_dir: !! fsentry.is_dir, + fsentry_is_dir: !!fsentry.is_dir, fsentry_name: fsentry.name, fsentry_size: fsentry.size, fsentry_accessed: fsentry.accessed, fsentry_modified: fsentry.modified, fsentry_created: fsentry.created, - } + }; } -async function gen_public_token(file_uuid, ttl = 24 * 60 * 60){ +async function gen_public_token (file_uuid, ttl = 24 * 60 * 60) { const { v4: uuidv4 } = require('uuid'); // get fsentry let fsentry = await uuid2fsentry(file_uuid); // fsentry not found - if(fsentry === false){ - throw {message: 'No entry found with this uid'}; + if ( fsentry === false ) { + throw { message: 'No entry found with this uid' }; } const uid = fsentry.uuid; @@ -1110,16 +1150,15 @@ async function gen_public_token(file_uuid, ttl = 24 * 60 * 60){ const db = services.get('database').get(DB_WRITE, 'filesystem'); // insert into DB - try{ - await db.write( - `UPDATE fsentries SET public_token = ? WHERE id = ?`, - [ - //token - token, - //fsentry_id - fsentry.id, - ]); - }catch(e){ + try { + await db.write('UPDATE fsentries SET public_token = ? WHERE id = ?', + [ + //token + token, + //fsentry_id + fsentry.id, + ]); + } catch (e) { console.log(e); return false; } @@ -1132,28 +1171,26 @@ async function gen_public_token(file_uuid, ttl = 24 * 60 * 60){ fsentry_type: contentType, fsentry_is_dir: fsentry.is_dir, fsentry_name: fsentry.name, - } + }; } -async function deleteUser(user_id){ +async function deleteUser (user_id) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); const svc_fs = services.get('filesystem'); // get a list of up to 5000 files owned by this user - for ( let offset=0; true; offset += 5000 ) { - let files = await db.read( - `SELECT uuid, bucket, bucket_region FROM fsentries WHERE user_id = ? AND is_dir = 0 LIMIT 5000 OFFSET `+offset, - [user_id] - ); - + for ( let offset = 0; true; offset += 5000 ) { + let files = await db.read(`SELECT uuid, bucket, bucket_region FROM fsentries WHERE user_id = ? AND is_dir = 0 LIMIT 5000 OFFSET ${ offset}`, + [user_id]); + if ( !files || files.length == 0 ) break; // delete all files from S3 - if(files !== null && files.length > 0){ - for(let i=0; i 0 ) { + for ( let i = 0; i < files.length; i++ ) { const node = await svc_fs.node(new NodeUIDSelector(files[i].uuid)); - + await node.provider.unlink({ context: Context.get(), override_immutable: true, @@ -1164,45 +1201,59 @@ async function deleteUser(user_id){ } // delete all fsentries from DB - await db.write(`DELETE FROM fsentries WHERE user_id = ?`,[user_id]); + await db.write('DELETE FROM fsentries WHERE user_id = ?', [user_id]); // delete user - await db.write(`DELETE FROM user WHERE id = ?`,[user_id]); + await db.write('DELETE FROM user WHERE id = ?', [user_id]); } -function subdomain(req){ +function subdomain (req) { if ( config.experimental_no_subdomain ) return 'api'; return req.hostname.slice(0, -1 * (config.domain.length + 1)); } -async function jwt_auth(req){ +async function jwt_auth (req) { let token; // HTTML Auth header - if(req.header && req.header('Authorization')) + if ( req.header && req.header('Authorization') ) + { token = req.header('Authorization'); + } // Cookie - else if(req.cookies && req.cookies[config.cookie_name]) + else if ( req.cookies && req.cookies[config.cookie_name] ) + { token = req.cookies[config.cookie_name]; + } // Auth token in URL - else if(req.query && req.query.auth_token) + else if ( req.query && req.query.auth_token ) + { token = req.query.auth_token; + } // Socket - else if(req.handshake && req.handshake.auth && req.handshake.auth.auth_token) + else if ( req.handshake && req.handshake.auth && req.handshake.auth.auth_token ) + { token = req.handshake.auth.auth_token; + } - if(!token || token === 'null') - throw('No auth token found'); - else if (typeof token !== 'string') - throw('token must be a string.') + if ( !token || token === 'null' ) + { + throw ('No auth token found'); + } + else if ( typeof token !== 'string' ) + { + throw ('token must be a string.'); + } else - token = token.replace('Bearer ', '') + { + token = token.replace('Bearer ', ''); + } - try{ + try { const svc_auth = Context.get('services').get('auth'); const actor = await svc_auth.authenticate_from_token(token); - if ( ! actor.type?.constructor?.name === 'UserActorType' ) { - throw({ + if ( !actor.type?.constructor?.name === 'UserActorType' ) { + throw ({ message: APIError.create('token_unsupported') .serialize(), }); @@ -1213,11 +1264,11 @@ async function jwt_auth(req){ user: actor.type.user, token: token, }; - }catch(e){ + } catch (e) { if ( ! (e instanceof APIError) ) { console.log('ERROR', e); } - throw(e.message); + throw (e.message); } } @@ -1226,85 +1277,89 @@ async function jwt_auth(req){ * * @param {*} fsentry_id */ - async function ancestors(fsentry_id){ +async function ancestors (fsentry_id) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); const ancestors = []; // first parent - let parent = await db.read("SELECT * FROM `fsentries` WHERE `id` = ? LIMIT 1", [fsentry_id]); - if(parent.length === 0){ + let parent = await db.read('SELECT * FROM `fsentries` WHERE `id` = ? LIMIT 1', [fsentry_id]); + if ( parent.length === 0 ) { return ancestors; } // get all subsequent parents - while(parent[0].parent_uid !== null){ + while ( parent[0].parent_uid !== null ) { const parent_fsentry = await uuid2fsentry(parent[0].parent_uid); - parent = await db.read("SELECT * FROM `fsentries` WHERE `id` = ? LIMIT 1", [parent_fsentry.id]); - if(parent[0].length !== 0){ - ancestors.push(parent[0]) + parent = await db.read('SELECT * FROM `fsentries` WHERE `id` = ? LIMIT 1', [parent_fsentry.id]); + if ( parent[0].length !== 0 ) { + ancestors.push(parent[0]); } } return ancestors; } -function hyphenize_confirm_code(email_confirm_code){ +function hyphenize_confirm_code (email_confirm_code) { email_confirm_code = email_confirm_code.toString(); email_confirm_code = - email_confirm_code[0] + + `${email_confirm_code[0] + email_confirm_code[1] + - email_confirm_code[2] + - '-' + - email_confirm_code[3] + - email_confirm_code[4] + - email_confirm_code[5]; + email_confirm_code[2] + }-${ + email_confirm_code[3] + }${email_confirm_code[4] + }${email_confirm_code[5]}`; return email_confirm_code; } -async function username_exists(username){ +async function username_exists (username) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); - let rows = await db.read(`SELECT EXISTS(SELECT 1 FROM user WHERE username=?) AS username_exists`, [username]); - if(rows[0].username_exists) + let rows = await db.read('SELECT EXISTS(SELECT 1 FROM user WHERE username=?) AS username_exists', [username]); + if ( rows[0].username_exists ) + { return true; + } } -async function app_name_exists(name){ +async function app_name_exists (name) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_READ, 'filesystem'); - let rows = await db.read(`SELECT EXISTS(SELECT 1 FROM apps WHERE apps.name=?) AS app_name_exists`, [name]); - if(rows[0].app_name_exists) + let rows = await db.read('SELECT EXISTS(SELECT 1 FROM apps WHERE apps.name=?) AS app_name_exists', [name]); + if ( rows[0].app_name_exists ) + { return true; + } const svc_oldAppName = services.get('old-app-name'); const name_info = await svc_oldAppName.check_app_name(name); if ( name_info ) return true; } -function send_email_verification_code(email_confirm_code, email){ +function send_email_verification_code (email_confirm_code, email) { const svc_email = Context.get('services').get('email'); svc_email.send_email({ email }, 'email_verification_code', { code: hyphenize_confirm_code(email_confirm_code), - }) + }); } -function send_email_verification_token(email_confirm_token, email, user_uuid){ +function send_email_verification_token (email_confirm_token, email, user_uuid) { const svc_email = Context.get('services').get('email'); const link = `${config.origin}/confirm-email-by-token?user_uuid=${user_uuid}&token=${email_confirm_token}`; svc_email.send_email({ email }, 'email_verification_link', { link }); } -function generate_random_str(length) { +function generate_random_str (length) { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; const charactersLength = characters.length; for ( let i = 0; i < length; i++ ) { - result += characters.charAt(Math.floor(Math.random() * - charactersLength)); - } - return result; + result += characters.charAt(Math.floor(Math.random() * + charactersLength)); + } + return result; } /** @@ -1314,28 +1369,27 @@ function generate_random_str(length) { * @returns {string} The time represented in the format: 'X years Y days Z hours A minutes B seconds'. * @throws {TypeError} If the `seconds` parameter is not a number. */ -function seconds_to_string(seconds) { +function seconds_to_string (seconds) { const numyears = Math.floor(seconds / 31536000); const numdays = Math.floor((seconds % 31536000) / 86400); const numhours = Math.floor(((seconds % 31536000) % 86400) / 3600); const numminutes = Math.floor((((seconds % 31536000) % 86400) % 3600) / 60); const numseconds = (((seconds % 31536000) % 86400) % 3600) % 60; - return numyears + " years " + numdays + " days " + numhours + " hours " + numminutes + " minutes " + numseconds + " seconds"; + return `${numyears } years ${ numdays } days ${ numhours } hours ${ numminutes } minutes ${ numseconds } seconds`; } - /** * returns a list of apps that could open the fsentry, ranked by relevance * @param {*} fsentry * @param {*} options */ -async function suggest_app_for_fsentry(fsentry, options){ +async function suggest_app_for_fsentry (fsentry, options) { const svc_performanceMonitor = services.get('performance-monitor'); - const monitor = svc_performanceMonitor.createContext("suggest_app_for_fsentry"); + const monitor = svc_performanceMonitor.createContext('suggest_app_for_fsentry'); const suggested_apps_promises = []; let content_type = mime.contentType(fsentry.name); - if( ! content_type ) content_type = ''; + if ( ! content_type ) content_type = ''; // IIFE just so fsname can stay `const` const fsname = (() => { @@ -1348,10 +1402,10 @@ async function suggest_app_for_fsentry(fsentry, options){ return fsname; })(); const file_extension = _path.extname(fsname).toLowerCase(); - + const any_of = (list, name) => { return list.some(v => name.endsWith(v)); - } + }; //--------------------------------------------- // Code @@ -1417,59 +1471,59 @@ async function suggest_app_for_fsentry(fsentry, options){ ]; if ( any_of(exts_code, fsname) || !fsname.includes('.') ) { - suggested_apps_promises.push(get_app({name: 'code'})) - suggested_apps_promises.push(get_app({name: 'editor'})) + suggested_apps_promises.push(get_app({ name: 'code' })); + suggested_apps_promises.push(get_app({ name: 'editor' })); } //--------------------------------------------- // Editor //--------------------------------------------- - if( + if ( fsname.endsWith('.txt') || // files with no extension !fsname.includes('.') - ){ - suggested_apps_promises.push(get_app({name: 'editor'})) - suggested_apps_promises.push(get_app({name: 'code'})) + ) { + suggested_apps_promises.push(get_app({ name: 'editor' })); + suggested_apps_promises.push(get_app({ name: 'code' })); } //--------------------------------------------- // Markus //--------------------------------------------- - if(fsname.endsWith('.md')){ - suggested_apps_promises.push(get_app({name: 'markus'})) + if ( fsname.endsWith('.md') ) { + suggested_apps_promises.push(get_app({ name: 'markus' })); } //--------------------------------------------- // Viewer //--------------------------------------------- - if( + if ( fsname.endsWith('.jpg') || fsname.endsWith('.png') || fsname.endsWith('.webp') || fsname.endsWith('.svg') || fsname.endsWith('.bmp') || fsname.endsWith('.jpeg') - ){ - suggested_apps_promises.push(get_app({name: 'viewer'})); + ) { + suggested_apps_promises.push(get_app({ name: 'viewer' })); } //--------------------------------------------- // Draw //--------------------------------------------- - if( + if ( fsname.endsWith('.bmp') || content_type.startsWith('image/') - ){ - suggested_apps_promises.push(get_app({name: 'draw'})); + ) { + suggested_apps_promises.push(get_app({ name: 'draw' })); } //--------------------------------------------- // PDF //--------------------------------------------- - if(fsname.endsWith('.pdf')){ - suggested_apps_promises.push(get_app({name: 'pdf'})); + if ( fsname.endsWith('.pdf') ) { + suggested_apps_promises.push(get_app({ name: 'pdf' })); } //--------------------------------------------- // Player //--------------------------------------------- - if( + if ( fsname.endsWith('.mp4') || fsname.endsWith('.webm') || fsname.endsWith('.mpg') || @@ -1477,8 +1531,8 @@ async function suggest_app_for_fsentry(fsentry, options){ fsname.endsWith('.mp3') || fsname.endsWith('.m4a') || fsname.endsWith('.ogg') - ){ - suggested_apps_promises.push(get_app({name: 'player'})); + ) { + suggested_apps_promises.push(get_app({ name: 'player' })); } //--------------------------------------------- @@ -1486,16 +1540,18 @@ async function suggest_app_for_fsentry(fsentry, options){ //--------------------------------------------- const apps = kv.get(`assocs:${file_extension.slice(1)}:apps`) ?? []; - monitor.label("third party associations"); + monitor.label('third party associations'); for ( const app_id of apps ) { suggested_apps_promises.push((async () => { // retrieve app from DB - const third_party_app = await get_app({id: app_id}) + const third_party_app = await get_app({ id: app_id }); if ( ! third_party_app ) return; // only add if the app is approved for opening items or the app is owned by this user - if( third_party_app.approved_for_opening_items || - (options !== undefined && options.user !== undefined && options.user.id === third_party_app.owner_user_id)) + if ( third_party_app.approved_for_opening_items || + (options !== undefined && options.user !== undefined && options.user.id === third_party_app.owner_user_id) ) + { return third_party_app; + } })()); } monitor.stamp(); @@ -1506,65 +1562,71 @@ async function suggest_app_for_fsentry(fsentry, options){ return suggested_apps.filter((suggested_app, pos, self) => { // Remove any null values caused by calling `get_app()` for apps that don't exist. // This happens on self-host because we don't include `code`, among others. - if (!suggested_app) + if ( ! suggested_app ) + { return false; + } // Remove any duplicate entries return self.indexOf(suggested_app) === pos; }); } -async function get_taskbar_items(user, { icon_size, no_icons } = {}) { +async function get_taskbar_items (user, { icon_size, no_icons } = {}) { /** @type BaseDatabaseAccessService */ const db = services.get('database').get(DB_WRITE, 'filesystem'); let taskbar_items_from_db = []; // If taskbar items don't exist (specifically NULL) // add default apps. - if(!user.taskbar_items){ + if ( ! user.taskbar_items ) { taskbar_items_from_db = [ - {name: 'app-center', type: 'app'}, - {name: 'dev-center', type: 'app'}, - {name: 'editor', type: 'app'}, - {name: 'code', type: 'app'}, - {name: 'camera', type: 'app'}, - {name: 'recorder', type: 'app'}, + { name: 'app-center', type: 'app' }, + { name: 'dev-center', type: 'app' }, + { name: 'editor', type: 'app' }, + { name: 'code', type: 'app' }, + { name: 'camera', type: 'app' }, + { name: 'recorder', type: 'app' }, ]; - await db.write( - `UPDATE user SET taskbar_items = ? WHERE id = ?`, - [ - JSON.stringify(taskbar_items_from_db), - user.id, - ] - ); + await db.write('UPDATE user SET taskbar_items = ? WHERE id = ?', + [ + JSON.stringify(taskbar_items_from_db), + user.id, + ]); invalidate_cached_user(user); } // there are items from before - else{ + else { try { taskbar_items_from_db = JSON.parse(user.taskbar_items); - }catch(e){ + } catch (e) { // ignore errors } } // get apps that these taskbar items represent let taskbar_items = []; - for (let index = 0; index < taskbar_items_from_db.length; index++) { + for ( let index = 0; index < taskbar_items_from_db.length; index++ ) { const taskbar_item_from_db = taskbar_items_from_db[index]; if ( taskbar_item_from_db.type !== 'app' ) continue; if ( taskbar_item_from_db.name === 'explorer' ) continue; let item = {}; - if(taskbar_item_from_db.name) - item = await get_app({name: taskbar_item_from_db.name}); - else if(taskbar_item_from_db.id) - item = await get_app({id: taskbar_item_from_db.id}); - else if(taskbar_item_from_db.uid) - item = await get_app({uid: taskbar_item_from_db.uid}); + if ( taskbar_item_from_db.name ) + { + item = await get_app({ name: taskbar_item_from_db.name }); + } + else if ( taskbar_item_from_db.id ) + { + item = await get_app({ id: taskbar_item_from_db.id }); + } + else if ( taskbar_item_from_db.uid ) + { + item = await get_app({ uid: taskbar_item_from_db.uid }); + } // if item not found, skip it - if(!item) continue; + if ( ! item ) continue; // delete sensitive attributes delete item.id; @@ -1588,56 +1650,72 @@ async function get_taskbar_items(user, { icon_size, no_icons } = {}) { } // add to final object - taskbar_items.push(item) + taskbar_items.push(item); } return taskbar_items; } -function validate_signature_auth(url, action, options = {}) { +function validate_signature_auth (url, action, options = {}) { const query = new URL(url).searchParams; - if(!query.get('uid')) - throw {message: '`uid` is required for signature-based authentication.'} - else if(!action) - throw {message: '`action` is required for signature-based authentication.'} - else if(!query.get('expires')) - throw {message: '`expires` is required for signature-based authentication.'} - else if(!query.get('signature')) - throw {message: '`signature` is required for signature-based authentication.'} - + if ( ! query.get('uid') ) + { + throw { message: '`uid` is required for signature-based authentication.' }; + } + else if ( ! action ) + { + throw { message: '`action` is required for signature-based authentication.' }; + } + else if ( ! query.get('expires') ) + { + throw { message: '`expires` is required for signature-based authentication.' }; + } + else if ( ! query.get('signature') ) + { + throw { message: '`signature` is required for signature-based authentication.' }; + } + if ( options.uid ) { if ( query.get('uid') !== options.uid ) { - throw {message: 'Authentication failed. `uid` does not match.'} + throw { message: 'Authentication failed. `uid` does not match.' }; } } const expired = query.get('expires') && (query.get('expires') < Date.now() / 1000); // expired? - if(expired) - throw {message: 'Authentication failed. Signature expired.'} + if ( expired ) + { + throw { message: 'Authentication failed. Signature expired.' }; + } const uid = query.get('uid'); const secret = config.url_signature_secret; const sha256 = require('js-sha256').sha256; // before doing anything, see if this signature is valid for 'write' action, if yes that means every action is allowed - if(!expired && query.get('signature') === sha256(`${uid}/write/${secret}/${query.get('expires')}`)) + if ( !expired && query.get('signature') === sha256(`${uid}/write/${secret}/${query.get('expires')}`) ) + { return true; + } // if not, check specific actions - else if(!expired && query.get('signature') === sha256(`${uid}/${action}/${secret}/${query.get('expires')}`)) + else if ( !expired && query.get('signature') === sha256(`${uid}/${action}/${secret}/${query.get('expires')}`) ) + { return true; + } // auth failed else - throw {message: 'Authentication failed'} + { + throw { message: 'Authentication failed' }; + } } -function get_url_from_req(req) { - return req.protocol + '://' + req.get('host') + req.originalUrl; +function get_url_from_req (req) { + return `${req.protocol }://${ req.get('host') }${req.originalUrl}`; } -async function mv(options){ +async function mv (options) { throw new Error('legacy mv function called'); } @@ -1653,7 +1731,7 @@ async function mv(options){ */ function number_format (number, decimals, dec_point, thousands_sep) { // Strip all characters but numerical ones. - number = (number + '').replace(/[^0-9+\-Ee.]/g, ''); + number = (`${number }`).replace(/[^0-9+\-Ee.]/g, ''); let n = !isFinite(+number) ? 0 : +number, prec = !isFinite(+decimals) ? 0 : Math.abs(decimals), sep = (typeof thousands_sep === 'undefined') ? ',' : thousands_sep, @@ -1661,14 +1739,14 @@ function number_format (number, decimals, dec_point, thousands_sep) { s = '', toFixedFix = function (n, prec) { const k = Math.pow(10, prec); - return '' + Math.round(n * k) / k; + return `${ Math.round(n * k) / k}`; }; // Fix for IE parseFloat(0.55).toFixed(0) = 0; - s = (prec ? toFixedFix(n, prec) : '' + Math.round(n)).split('.'); - if (s[0].length > 3) { + s = (prec ? toFixedFix(n, prec) : `${ Math.round(n)}`).split('.'); + if ( s[0].length > 3 ) { s[0] = s[0].replace(/\B(?=(?:\d{3})+(?!\d))/g, sep); } - if ((s[1] || '').length < prec) { + if ( (s[1] || '').length < prec ) { s[1] = s[1] || ''; s[1] += new Array(prec - s[1].length + 1).join('0'); } diff --git a/src/backend/src/html_footer.js b/src/backend/src/html_footer.js index 55f80950d9..9cec1aa731 100644 --- a/src/backend/src/html_footer.js +++ b/src/backend/src/html_footer.js @@ -18,58 +18,58 @@ */ const config = require('./config'); -function html_footer(options) { - let html = ``; - if(options.show_footer ?? false){ - html += `
`; - html += `
`; - html += `
`; - html += ``; - html += ``; - html += ``; - html += ``; - html += `
`; +function html_footer (options) { + let html = ''; + if ( options.show_footer ?? false ) { + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += ''; + html += '
'; - html += `
`; - html += `
`; + html += '
'; + html += '
'; - html += `
`; - html += `
`; + html += '
'; + html += '
'; - html += `
`; - html += `
`; + html += '
'; + html += '
'; - html += `
`; - html += `
Quick Links
`; - html += ``; - html += `
`; - html += `
`; - // social - html += `
` - html += `

Puter Technologies Inc. © ${new Date().getFullYear()}

`; - html += ``; - html += ``; - html += ``; - html += `
`; - html += `
`; - html += ``; + html += '
'; + html += '
Quick Links
'; + html += ''; + html += '
'; + html += '
'; + // social + html += '
'; + html += `

Puter Technologies Inc. © ${new Date().getFullYear()}

`; + html += ''; + html += ''; + html += ''; + html += '
'; + html += '
'; + html += ''; } - html += ``; - html += ``; - if(options.jsfiles && options.jsfiles.length > 0){ - options.jsfiles.forEach(jsfile => { - html += ``; - }); - } - html += ``; - html += ``; - return html; + html += ``; + html += ''; + if ( options.jsfiles && options.jsfiles.length > 0 ) { + options.jsfiles.forEach(jsfile => { + html += ``; + }); + } + html += ''; + html += ''; + return html; } module.exports = html_footer; \ No newline at end of file diff --git a/src/backend/src/html_head.js b/src/backend/src/html_head.js index ac5b325be5..447bb76995 100644 --- a/src/backend/src/html_head.js +++ b/src/backend/src/html_head.js @@ -16,24 +16,24 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const config = require('./config') -const {encode} = require('html-entities'); +const config = require('./config'); +const { encode } = require('html-entities'); -function html_head(options) { - let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`; - let html = ``; - html += ``; - html += ``; - html += ``; - // meta tags - html += ``; - html += ``; - html += ``; - html += ``; - // title - html += `${encode(options.title ?? 'Puter')}`; - // favicons - html += ` +function html_head (options) { + let canonical_url = `${config.origin}/${options.page === 'index' ? '' : options.page}`; + let html = ''; + html += ''; + html += ``; + html += ''; + // meta tags + html += ''; + html += ''; + html += ``; + html += ''; + // title + html += `${encode(options.title ?? 'Puter')}`; + // favicons + html += ` @@ -51,58 +51,58 @@ function html_head(options) { `; - // Roboto font - html += ``; + // Roboto font + html += ''; - // canonical link - html += ``; + // canonical link + html += ``; - // preload images - if(options.page === 'index'){ - html += ``; - html += ``; - } + // preload images + if ( options.page === 'index' ) { + html += ''; + html += ''; + } - // Facebook meta tags - html += ``; - html += ``; - html += ``; - html += ``; - html += ``; + // Facebook meta tags + html += ``; + html += ''; + html += ``; + html += ``; + html += ``; - // Twitter meta tags - html += ``; - html += ``; - html += ``; - html += ``; - html += ``; - html += ``; + // Twitter meta tags + html += ''; + html += ``; + html += ``; + html += ``; + html += ``; + html += ``; - // CSS - html += ``; - html += ``; + // CSS + html += ''; + html += ''; - html += ``; - html += ``; - if(options.show_navbar ?? false){ - html += `
`; - html += `
`; - html += `
`; - html +=`
`; - html += ``; - html += ``; - html += ``; - html +=`
`; - - html += ``; + html += ''; + html += ``; + if ( options.show_navbar ?? false ) { + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; - html += `
`; - - html += `
`; - } - - html += ``; - return html; + html += ''; + + html += '
'; + + html += '
'; + } + + html += ''; + return html; } module.exports = html_head; \ No newline at end of file diff --git a/src/backend/src/index.js b/src/backend/src/index.js index 1ff54e332c..3699ce65a8 100644 --- a/src/backend/src/index.js +++ b/src/backend/src/index.js @@ -16,18 +16,17 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -"use strict" +'use strict'; -const { Kernel } = require("./Kernel"); -const CoreModule = require("./CoreModule"); -const { CaptchaModule } = require("./modules/captcha/CaptchaModule"); // Add CaptchaModule +const { Kernel } = require('./Kernel'); +const CoreModule = require('./CoreModule'); +const { CaptchaModule } = require('./modules/captcha/CaptchaModule'); // Add CaptchaModule const testlaunch = () => { const k = new Kernel(); k.add_module(new CoreModule()); k.add_module(new CaptchaModule()); // Register the CaptchaModule k.boot(); -} - +}; module.exports = { testlaunch }; diff --git a/src/backend/src/kernel/modutil.js b/src/backend/src/kernel/modutil.js index a719a8a45c..8475cf2a2e 100644 --- a/src/backend/src/kernel/modutil.js +++ b/src/backend/src/kernel/modutil.js @@ -1,10 +1,10 @@ const fs = require('fs').promises; const path = require('path'); -async function prependToJSFiles(directory, snippet) { +async function prependToJSFiles (directory, snippet) { const jsExtensions = new Set(['.js', '.cjs', '.mjs', '.ts']); - async function processDirectory(dir) { + async function processDirectory (dir) { try { const entries = await fs.readdir(dir, { withFileTypes: true }); const promises = []; @@ -14,7 +14,7 @@ async function prependToJSFiles(directory, snippet) { if ( entry.isDirectory() ) { // Skip common directories that shouldn't be modified - if ( !shouldSkipDirectory(entry.name) ) { + if ( ! shouldSkipDirectory(entry.name) ) { promises.push(processDirectory(fullPath)); } } else if ( entry.isFile() && jsExtensions.has(path.extname(entry.name)) ) { @@ -30,7 +30,7 @@ async function prependToJSFiles(directory, snippet) { } } - function shouldSkipDirectory(dirName) { + function shouldSkipDirectory (dirName) { const skipDirs = new Set([ 'node_modules', 'gui', @@ -40,7 +40,7 @@ async function prependToJSFiles(directory, snippet) { return false; } - async function prependToFile(filePath, snippet) { + async function prependToFile (filePath, snippet) { try { const content = await fs.readFile(filePath, 'utf8'); if ( content.startsWith('//!no-prepend') ) return; diff --git a/src/backend/src/libraries/ArrayUtil.js b/src/backend/src/libraries/ArrayUtil.js index c9a70c9281..18d01f4fb6 100644 --- a/src/backend/src/libraries/ArrayUtil.js +++ b/src/backend/src/libraries/ArrayUtil.js @@ -18,35 +18,31 @@ */ class ArrayUtil extends use.Library { /** - * - * @param {*} marked_map - * @param {*} subject + * + * @param {*} marked_map + * @param {*} subject */ remove_marked_items (marked_map, subject) { - for ( let i=0 ; i < marked_map.length ; i++ ) { + for ( let i = 0 ; i < marked_map.length ; i++ ) { let ii = marked_map[i]; // track: type check if ( ! Number.isInteger(ii) ) { - throw new Error( - 'marked_map can only contain integers' - ); + throw new Error('marked_map can only contain integers'); } // track: bounds check if ( ii < 0 && ii >= subject.length ) { - throw new Error( - 'each item in `marked_map` must be within that bounds ' + - 'of `subject`' - ); + throw new Error('each item in `marked_map` must be within that bounds ' + + 'of `subject`'); } } marked_map.sort((a, b) => b - a); - - for ( let i=0 ; i < marked_map.length ; i++ ) { + + for ( let i = 0 ; i < marked_map.length ; i++ ) { let ii = marked_map[i]; subject.splice(ii, 1); } - + return subject; } @@ -54,7 +50,8 @@ class ArrayUtil extends use.Library { // inner indices { const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + ]; // 0 1 2 3 4 5 6 7 const marked_map = [2, 5]; this.remove_marked_items(marked_map, subject); @@ -63,27 +60,30 @@ class ArrayUtil extends use.Library { // left edge { const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + ]; // 0 1 2 3 4 5 6 7 - const marked_map = [0] + const marked_map = [0]; this.remove_marked_items(marked_map, subject); assert(() => subject.join('') === 'bcdefgh'); } // right edge { const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + ]; // 0 1 2 3 4 5 6 7 - const marked_map = [7] + const marked_map = [7]; this.remove_marked_items(marked_map, subject); assert(() => subject.join('') === 'abcdefg'); } // both edges { const subject = [ - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']; + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + ]; // 0 1 2 3 4 5 6 7 - const marked_map = [0, 7] + const marked_map = [0, 7]; this.remove_marked_items(marked_map, subject); assert(() => subject.join('') === 'bcdefg'); } diff --git a/src/backend/src/libraries/LibTypeTagged.js b/src/backend/src/libraries/LibTypeTagged.js index 4ff0feeae5..707cce176e 100644 --- a/src/backend/src/libraries/LibTypeTagged.js +++ b/src/backend/src/libraries/LibTypeTagged.js @@ -16,79 +16,87 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { whatis } = require("../util/langutil"); +const { whatis } = require('../util/langutil'); class LibTypeTagged extends use.Library { process (o) { const could_be = whatis(o) === 'object' || Array.isArray(o); - if ( ! could_be ) return { - $: 'error', - code: 'invalid-type', - message: 'should be object or array', - }; - + if ( ! could_be ) { + return { + $: 'error', + code: 'invalid-type', + message: 'should be object or array', + }; + } + const intermediate = this.get_intermediate_(o); - - if ( ! intermediate.type ) return { - $: 'error', - code: 'missing-type-param', - message: 'type parameter is missing', - }; - + + if ( ! intermediate.type ) { + return { + $: 'error', + code: 'missing-type-param', + message: 'type parameter is missing', + }; + } + return this.intermediate_to_standard_(intermediate); } - + intermediate_to_standard_ (intermediate) { const out = {}; out.$ = intermediate.type; for ( const k in intermediate.meta ) { - out['$' + k] = intermediate.meta[k]; + out[`$${ k}`] = intermediate.meta[k]; } for ( const k in intermediate.body ) { out[k] = intermediate.body[k]; } return out; } - + get_intermediate_ (o) { if ( Array.isArray(o) ) { return this.process_array_(o); } - + if ( o['$'] === '$meta-body' ) { return this.process_structured_(o); } - + return this.process_standard_(o); } - + process_array_ (a) { - if ( a.length <= 1 || a.length > 3 ) return { - $: 'error', - code: 'invalid-array-length', - message: 'tag-typed arrays should have 1-3 elements', - }; - + if ( a.length <= 1 || a.length > 3 ) { + return { + $: 'error', + code: 'invalid-array-length', + message: 'tag-typed arrays should have 1-3 elements', + }; + } + const [type, body = {}, meta = {}] = a; - + return { $: '$', type, body, meta }; } - + process_structured_ (o) { - if ( ! o.hasOwnProperty('type') ) return { - $: 'error', - code: 'missing-type-property', - message: 'missing "type" property' - }; - + if ( ! o.hasOwnProperty('type') ) { + return { + $: 'error', + code: 'missing-type-property', + message: 'missing "type" property', + }; + } + return { $: '$', ...o }; } - + process_standard_ (o) { const type = o.$; const meta = {}; const body = {}; - + for ( const k in o ) { if ( k === '$' ) continue; if ( k.startsWith('$') ) { @@ -97,7 +105,7 @@ class LibTypeTagged extends use.Library { body[k] = o[k]; } } - + return { $: '$', type, meta, body }; } } diff --git a/src/backend/src/middleware/abuse.js b/src/backend/src/middleware/abuse.js index 5f718686c3..81c2987fc8 100644 --- a/src/backend/src/middleware/abuse.js +++ b/src/backend/src/middleware/abuse.js @@ -16,9 +16,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../api/APIError"); -const config = require("../config"); -const { Context } = require("../util/context"); +const APIError = require('../api/APIError'); +const config = require('../config'); +const { Context } = require('../util/context'); const abuse = options => (req, res, next) => { if ( config.disable_abuse_checks ) { diff --git a/src/backend/src/middleware/anticsrf.js b/src/backend/src/middleware/anticsrf.js index e7844c577e..d983e4a3fc 100644 --- a/src/backend/src/middleware/anticsrf.js +++ b/src/backend/src/middleware/anticsrf.js @@ -1,32 +1,32 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../api/APIError"); +const APIError = require('../api/APIError'); /** * Creates an anti-CSRF middleware that validates CSRF tokens in incoming requests. * This middleware protects against Cross-Site Request Forgery attacks by verifying * that requests contain a valid anti-CSRF token in the request body. - * + * * @param {Object} options - Configuration options for the middleware * @returns {Function} Express middleware function that validates CSRF tokens - * + * * @example * // Apply anti-CSRF protection to a route * app.post('/api/secure-endpoint', anticsrf(), (req, res) => { @@ -46,7 +46,7 @@ const anticsrf = options => async (req, res, next) => { err.write(res); return; } - + next(); }; diff --git a/src/backend/src/middleware/auth.js b/src/backend/src/middleware/auth.js index ff2a37bae7..977eb8a318 100644 --- a/src/backend/src/middleware/auth.js +++ b/src/backend/src/middleware/auth.js @@ -16,16 +16,18 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -"use strict" +'use strict'; const APIError = require('../api/APIError'); const { UserActorType } = require('../services/auth/Actor'); const auth2 = require('./auth2'); -const auth = async (req, res, next)=>{ +const auth = async (req, res, next) => { let auth2_ok = false; - try{ + try { // Delegate to new middleware - await auth2(req, res, () => { auth2_ok = true; }); + await auth2(req, res, () => { + auth2_ok = true; + }); if ( ! auth2_ok ) return; // Everything using the old reference to the auth middleware @@ -37,9 +39,9 @@ const auth = async (req, res, next)=>{ next(); } // auth failed - catch(e){ + catch (e) { return res.status(401).send(e); } -} +}; -module.exports = auth \ No newline at end of file +module.exports = auth; \ No newline at end of file diff --git a/src/backend/src/middleware/auth2.js b/src/backend/src/middleware/auth2.js index a739979c27..bcd93591cd 100644 --- a/src/backend/src/middleware/auth2.js +++ b/src/backend/src/middleware/auth2.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const configurable_auth = require("./configurable_auth"); +const configurable_auth = require('./configurable_auth'); const auth2 = configurable_auth({ optional: false }); diff --git a/src/backend/src/middleware/configurable_auth.js b/src/backend/src/middleware/configurable_auth.js index 9895f2082b..1195614526 100644 --- a/src/backend/src/middleware/configurable_auth.js +++ b/src/backend/src/middleware/configurable_auth.js @@ -17,9 +17,9 @@ * along with this program. If not, see . */ const APIError = require('../api/APIError'); -const config = require("../config"); -const { LegacyTokenError } = require("../services/auth/AuthService"); -const { Context } = require("../util/context"); +const config = require('../config'); +const { LegacyTokenError } = require('../services/auth/AuthService'); +const { Context } = require('../util/context'); // The "/whoami" endpoint is a special case where we want to allow // a legacy token to be used for authentication. The "/whoami" @@ -33,7 +33,7 @@ const is_whoami = (req) => { // const subdomain = req.subdomains[res.subdomains.length - 1]; // if ( subdomain !== 'api' ) return; return true; -} +}; // TODO: Allow auth middleware to be used without requiring // authentication. This will allow us to use the auth middleware @@ -56,40 +56,48 @@ const configurable_auth = options => async (req, res, next) => { let token; // Auth token in body - if(req.body && req.body.auth_token) + if ( req.body && req.body.auth_token ) + { token = req.body.auth_token; + } // HTTML Auth header - else if (req.header && req.header('Authorization') && !req.header('Authorization').startsWith("Basic ") && req.header('Authorization') !== "Bearer") { // Bearer with no space is something office does + else if ( req.header && req.header('Authorization') && !req.header('Authorization').startsWith('Basic ') && req.header('Authorization') !== 'Bearer' ) { // Bearer with no space is something office does token = req.header('Authorization'); token = token.replace('Bearer ', '').trim(); if ( token === 'undefined' ) { APIError.create('unexpected_undefined', null, { - msg: `The Authorization token cannot be the string "undefined"` + msg: 'The Authorization token cannot be the string "undefined"', }); } } // Cookie - else if(req.cookies && req.cookies[config.cookie_name]) + else if ( req.cookies && req.cookies[config.cookie_name] ) + { token = req.cookies[config.cookie_name]; + } // Auth token in URL - else if(req.query && req.query.auth_token) + else if ( req.query && req.query.auth_token ) + { token = req.query.auth_token; + } // Socket - else if(req.handshake && req.handshake.query && req.handshake.query.auth_token) + else if ( req.handshake && req.handshake.query && req.handshake.query.auth_token ) + { token = req.handshake.query.auth_token; - - if(!token || token.startsWith("Basic ")) { + } + + if ( !token || token.startsWith('Basic ') ) { if ( optional ) { next(); return; } APIError.create('token_missing').write(res); return; - } else if (typeof token !== 'string') { + } else if ( typeof token !== 'string' ) { APIError.create('token_auth_failed').write(res); return; } else { - token = token.replace('Bearer ', '') + token = token.replace('Bearer ', ''); } // === Delegate to AuthService === @@ -113,14 +121,14 @@ const configurable_auth = options => async (req, res, next) => { const new_info = await svc_auth.check_session(token, { req, from_upgrade: true, - }) + }); context.set('actor', new_info.actor); context.set('user', new_info.user); req.new_token = new_info.token; req.token = new_info.token; req.user = new_info.user; req.actor = new_info.actor; - + if ( req.user?.suspended ) { throw APIError.create('forbidden'); } diff --git a/src/backend/src/middleware/featureflag.js b/src/backend/src/middleware/featureflag.js index 316908e949..836efa44ed 100644 --- a/src/backend/src/middleware/featureflag.js +++ b/src/backend/src/middleware/featureflag.js @@ -1,28 +1,28 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../api/APIError"); -const { Context } = require("../util/context"); +const APIError = require('../api/APIError'); +const { Context } = require('../util/context'); const featureflag = options => async (req, res, next) => { const { feature } = options; - + const context = Context.get(); const services = context.get('services'); const svc_featureFlag = services.get('feature-flag'); diff --git a/src/backend/src/middleware/measure.js b/src/backend/src/middleware/measure.js index a48220a980..ce43bca15c 100644 --- a/src/backend/src/middleware/measure.js +++ b/src/backend/src/middleware/measure.js @@ -15,14 +15,14 @@ const _intercept_req = ({ data, req, next }) => { const replaces = ['readable', 'pipe', 'on', 'once', 'removeListener']; for ( const replace of replaces ) { - const replacement = req_pass[replace] + const replacement = req_pass[replace]; Object.defineProperty(req, replace, { get () { if ( typeof replacement === 'function' ) { return replacement.bind(req_pass); } return replacement; - } + }, }); } } catch (e) { @@ -39,28 +39,28 @@ const _intercept_res = ({ data, res, next }) => { try { const org_write = res.write; const org_end = res.end; - + // Override the `write` method res.write = function (chunk, ...args) { - if (Buffer.isBuffer(chunk)) { - data.sz_outgoing += chunk.length; - } else if (typeof chunk === 'string') { - data.sz_outgoing += Buffer.byteLength(chunk); - } - return org_write.apply(res, [chunk, ...args]); + if ( Buffer.isBuffer(chunk) ) { + data.sz_outgoing += chunk.length; + } else if ( typeof chunk === 'string' ) { + data.sz_outgoing += Buffer.byteLength(chunk); + } + return org_write.apply(res, [chunk, ...args]); }; - + // Override the `end` method res.end = function (chunk, ...args) { - if (chunk) { - if (Buffer.isBuffer(chunk)) { - data.sz_outgoing += chunk.length; - } else if (typeof chunk === 'string') { - data.sz_outgoing += Buffer.byteLength(chunk); + if ( chunk ) { + if ( Buffer.isBuffer(chunk) ) { + data.sz_outgoing += chunk.length; + } else if ( typeof chunk === 'string' ) { + data.sz_outgoing += Buffer.byteLength(chunk); + } } - } - const result = org_end.apply(res, [chunk, ...args]); - return result; + const result = org_end.apply(res, [chunk, ...args]); + return result; }; } catch (e) { console.error(e); diff --git a/src/backend/src/middleware/subdomain.js b/src/backend/src/middleware/subdomain.js index 048ef60d6a..d578c4aa6f 100644 --- a/src/backend/src/middleware/subdomain.js +++ b/src/backend/src/middleware/subdomain.js @@ -21,10 +21,10 @@ * match it calls `next('route')` to skip the current route. * Be sure to use this before any middleware that might erroneously * block the request. - * + * * @param {string|string[]} allowedSubdomains - The subdomain to allow; * if an array, any of the subdomains in the array will be allowed. - * + * * @returns {function} - An express middleware function */ const subdomain = allowedSubdomains => { @@ -43,6 +43,6 @@ const subdomain = allowedSubdomains => { next(); }; -} +}; module.exports = subdomain; diff --git a/src/backend/src/middleware/verified.js b/src/backend/src/middleware/verified.js index f0a81b66f0..da66f91d27 100644 --- a/src/backend/src/middleware/verified.js +++ b/src/backend/src/middleware/verified.js @@ -16,9 +16,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const config = require("../config") +const config = require('../config'); -const verified = async (req, res, next)=>{ +const verified = async (req, res, next) => { if ( ! config.strict_email_verification_required ) { next(); return; @@ -36,8 +36,8 @@ const verified = async (req, res, next)=>{ res.status(400).send({ code: 'account_is_not_verified', - message: 'Account is not verified' + message: 'Account is not verified', }); -} +}; -module.exports = verified +module.exports = verified; diff --git a/src/backend/src/modules/apps/AppIconService.js b/src/backend/src/modules/apps/AppIconService.js index a9765008b5..ed43b12a77 100644 --- a/src/backend/src/modules/apps/AppIconService.js +++ b/src/backend/src/modules/apps/AppIconService.js @@ -1,43 +1,43 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { HLWrite } = require("../../filesystem/hl_operations/hl_write"); -const { LLMkdir } = require("../../filesystem/ll_operations/ll_mkdir"); -const { LLRead } = require("../../filesystem/ll_operations/ll_read"); -const { NodePathSelector } = require("../../filesystem/node/selectors"); -const { get_app } = require("../../helpers"); -const { Endpoint } = require("../../util/expressutil"); -const { buffer_to_stream, stream_to_buffer } = require("../../util/streamutil"); -const BaseService = require("../../services/BaseService.js"); +const { HLWrite } = require('../../filesystem/hl_operations/hl_write'); +const { LLMkdir } = require('../../filesystem/ll_operations/ll_mkdir'); +const { LLRead } = require('../../filesystem/ll_operations/ll_read'); +const { NodePathSelector } = require('../../filesystem/node/selectors'); +const { get_app } = require('../../helpers'); +const { Endpoint } = require('../../util/expressutil'); +const { buffer_to_stream, stream_to_buffer } = require('../../util/streamutil'); +const BaseService = require('../../services/BaseService.js'); -const ICON_SIZES = [16,32,64,128,256,512]; +const ICON_SIZES = [16, 32, 64, 128, 256, 512]; const DEFAULT_APP_ICON = require('./default-app-icon.js'); -const IconResult = require("./lib/IconResult.js"); +const IconResult = require('./lib/IconResult.js'); /** * AppIconService handles icon generation and serving for apps. - * + * * This is done by listening to the `app.new-icon` event which is * dispatched by AppES. `sharp` is used to resize the images to * pre-selected sizees in the `ICON_SIZES` constant defined above. - * + * * Icons are stored in and served from the `/system/app_icons` * directory. If the system user does not have this directory, * it will be created in the consolidation boot phase after @@ -49,8 +49,8 @@ class AppIconService extends BaseService { sharp: require('sharp'), bmp: require('sharp-bmp'), ico: require('sharp-ico'), - } - + }; + static ICON_SIZES = ICON_SIZES; /** @@ -76,18 +76,18 @@ class AppIconService extends BaseService { const { stream, mime, - } = await this.get_icon_stream({ app_uid, size, }) + } = await this.get_icon_stream({ app_uid, size }); res.set('Content-Type', mime); stream.pipe(res); }, }).attach(app); } - + get_sizes () { return this.constructor.ICON_SIZES; } - + async iconify_apps ({ apps, size }) { return await Promise.all(apps.map(async app => { const icon_result = await this.get_icon_stream({ @@ -104,7 +104,7 @@ class AppIconService extends BaseService { try { const buffer = await stream_to_buffer(icon_result.stream); const resp_data_url = `data:${icon_result.mime};base64,${buffer.toString('base64')}`; - + app.icon = resp_data_url; } catch (e) { this.errors.report('get-launch-apps:icon-stream', { @@ -127,14 +127,14 @@ class AppIconService extends BaseService { const input_mime = metadata.split(';')[0].split(':')[1]; // svg icons will be sent as-is - if (input_mime === 'image/svg+xml') { + if ( input_mime === 'image/svg+xml' ) { return { mime: 'image/svg+xml', get stream () { return buffer_to_stream(Buffer.from(data, 'base64')); }, data_url: app_icon, - } + }; } } @@ -147,7 +147,7 @@ class AppIconService extends BaseService { app_icon = app_icon || await (async () => { const app = await get_app({ uid: app_uid }); return app.icon || DEFAULT_APP_ICON; - })() + })(); const [metadata, base64] = app_icon.split(','); const mime = metadata.split(';')[0].split(':')[1]; const img = Buffer.from(base64, 'base64'); @@ -155,7 +155,7 @@ class AppIconService extends BaseService { mime, stream: buffer_to_stream(img), }; - } + }; if ( ! await node.exists() ) { return await get_fallback_icon(); @@ -169,7 +169,7 @@ class AppIconService extends BaseService { stream: await ll_read.run({ fsNode: node, actor: await svc_su.get_system_actor(), - }) + }), }; } catch (e) { this.errors.report('AppIconService.get_icon_stream', { @@ -187,7 +187,7 @@ class AppIconService extends BaseService { // let second_size = size > 16 ? size / 2 : 32; return await this.get_icon_stream({ - app_uid, size: second_size, tries: tries + 1 + app_uid, size: second_size, tries: tries + 1, }); } return await get_fallback_icon(); @@ -204,9 +204,7 @@ class AppIconService extends BaseService { } const svc_fs = this.services.get('filesystem'); - const dir_app_icons = await svc_fs.node( - new NodePathSelector('/system/app_icons') - ); + const dir_app_icons = await svc_fs.node(new NodePathSelector('/system/app_icons')); return this.dir_app_icons = dir_app_icons; } @@ -226,7 +224,7 @@ class AppIconService extends BaseService { return this.modules.sharp(input); } - + /** * AppIconService listens to this event to create the * `/system/app_icons` directory if it does not exist, @@ -240,9 +238,7 @@ class AppIconService extends BaseService { const dir_system = await svc_user.get_system_dir(); // Ensure app icons directory exists - const dir_app_icons = await svc_fs.node( - new NodePathSelector('/system/app_icons') - ); + const dir_app_icons = await svc_fs.node(new NodePathSelector('/system/app_icons')); if ( ! await dir_app_icons.exists() ) { const ll_mkdir = new LLMkdir(); await ll_mkdir.run({ @@ -278,7 +274,7 @@ class AppIconService extends BaseService { metadata, input, }); - + // NOTE: A stream would be more ideal than a buffer here // but we have no way of knowing the output size // before we finish processing the image. @@ -286,7 +282,7 @@ class AppIconService extends BaseService { .resize(size) .png() .toBuffer(); - + const sys_actor = await svc_su.get_system_actor(); const hl_write = new HLWrite(); await hl_write.run({ @@ -304,7 +300,7 @@ class AppIconService extends BaseService { stream: buffer_to_stream(output), }, }); - }) + }); })()); } await Promise.all(icon_jobs); diff --git a/src/backend/src/modules/apps/AppInformationService.js b/src/backend/src/modules/apps/AppInformationService.js index d4e38798ea..f6d25cd387 100644 --- a/src/backend/src/modules/apps/AppInformationService.js +++ b/src/backend/src/modules/apps/AppInformationService.js @@ -18,9 +18,9 @@ * along with this program. If not, see . */ const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise; -const { MINUTE } = require("@heyputer/putility").libs.time; -const { origin_from_url } = require("../../util/urlutil"); -const { DB_READ } = require("../../services/database/consts"); +const { MINUTE } = require('@heyputer/putility').libs.time; +const { origin_from_url } = require('../../util/urlutil'); +const { DB_READ } = require('../../services/database/consts'); const BaseService = require('../../services/BaseService'); // Currently leaks memory (not sure why yet, but icons are a factor) @@ -51,19 +51,19 @@ class AppInformationService extends BaseService { 'day': '%Y-%m-%d', 'week': '%Y-%U', 'month': '%Y-%m', - 'year': '%Y' + 'year': '%Y', }; // ClickHouse date format mapping for different groupings this.clickhouseGroupByFormats = { - 'hour': "toStartOfHour(fromUnixTimestamp(ts))", - 'day': "toStartOfDay(fromUnixTimestamp(ts))", - 'week': "toStartOfWeek(fromUnixTimestamp(ts))", - 'month': "toStartOfMonth(fromUnixTimestamp(ts))", - 'year': "toStartOfYear(fromUnixTimestamp(ts))" + 'hour': 'toStartOfHour(fromUnixTimestamp(ts))', + 'day': 'toStartOfDay(fromUnixTimestamp(ts))', + 'week': 'toStartOfWeek(fromUnixTimestamp(ts))', + 'month': 'toStartOfMonth(fromUnixTimestamp(ts))', + 'year': 'toStartOfYear(fromUnixTimestamp(ts))', }; } - + ['__on_boot.consolidation'] () { (async () => { // await new Promise(rslv => setTimeout(rslv, 500)) @@ -72,7 +72,7 @@ class AppInformationService extends BaseService { await this._refresh_app_cache(); /** * Refreshes the application cache by querying the database for all apps and updating the key-value store. - * + * * This method is called periodically to ensure that the in-memory cache reflects the latest * state from the database. It uses the 'database' service to fetch app data and then updates * multiple cache entries for quick lookups by name, ID, and UID. @@ -88,7 +88,7 @@ class AppInformationService extends BaseService { /** * Refreshes the cache of recently opened apps. * This method updates the 'recent' collection with the UIDs of apps sorted by their most recent timestamp. - * + * * @async * @returns {Promise} A promise that resolves when the cache has been refreshed. */ @@ -102,7 +102,7 @@ class AppInformationService extends BaseService { * Refreshes the app referral statistics. * This method is computationally expensive and thus runs less frequently. * It queries the database for user counts referred by each app's origin URL. - * + * * @async */ asyncSafeSetInterval(async () => { @@ -114,7 +114,7 @@ class AppInformationService extends BaseService { * Refreshes the recent cache by updating the list of recently added or updated apps. * This method fetches all app data, filters for approved apps, sorts them by timestamp, * and updates the `this.collections.recent` array with the UIDs of the most recent 50 apps. - * + * * @async * @private */ @@ -134,14 +134,13 @@ class AppInformationService extends BaseService { */ asyncSafeSetInterval(async () => { this._refresh_tags(); - } , 120 * 1000); + }, 120 * 1000); })(); } - /** * Retrieves and returns statistical data for a specific application over different time periods. - * + * * This method fetches various metrics such as the number of times the app has been opened, * the count of unique users who have opened the app, and the number of referrals attributed to the app. * It supports different time periods such as today, yesterday, past 7 days, past 30 days, and all time. @@ -155,13 +154,13 @@ class AppInformationService extends BaseService { * - {Object} user_count - Uniqu>e user counts for different time periods * - {number|null} referral_count - The number of referrals (all-time only) */ - async get_stats(app_uid, options = {}) { + async get_stats (app_uid, options = {}) { let period = options.period ?? 'all'; let stats_grouping = options.grouping; let app_creation_ts = options.created_at; // Check cache first if period is 'all' and no grouping is requested - if (period === 'all' && !stats_grouping) { + if ( period === 'all' && !stats_grouping ) { const key_open_count = `apps:open_count:uid:${app_uid}`; const key_user_count = `apps:user_count:uid:${app_uid}`; const key_referral_count = `apps:referral_count:uid:${app_uid}`; @@ -169,14 +168,14 @@ class AppInformationService extends BaseService { const [cached_open_count, cached_user_count, cached_referral_count] = await Promise.all([ kv.get(key_open_count), kv.get(key_user_count), - kv.get(key_referral_count) + kv.get(key_referral_count), ]); - if (cached_open_count !== null && cached_user_count !== null) { + if ( cached_open_count !== null && cached_user_count !== null ) { return { open_count: parseInt(cached_open_count), user_count: parseInt(cached_user_count), - referral_count: cached_referral_count + referral_count: cached_referral_count, }; } } @@ -187,123 +186,121 @@ class AppInformationService extends BaseService { const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - switch(period) { - case 'today': - return { - start: today.getTime(), - end: now.getTime() - }; - case 'yesterday': { - const yesterday = new Date(today); - yesterday.setDate(yesterday.getDate() - 1); - return { - start: yesterday.getTime(), - end: today.getTime() - 1 - }; - } - case '7d': { - const weekAgo = new Date(now); - weekAgo.setDate(weekAgo.getDate() - 7); - return { - start: weekAgo.getTime(), - end: now.getTime() - }; - } - case '30d': { - const monthAgo = new Date(now); - monthAgo.setDate(monthAgo.getDate() - 30); - return { - start: monthAgo.getTime(), - end: now.getTime() - }; - } - case 'this_week': { - const firstDayOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); - return { - start: firstDayOfWeek.getTime(), - end: now.getTime() - }; - } - case 'last_week': { - const firstDayOfLastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() - 7); - const firstDayOfThisWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); - return { - start: firstDayOfLastWeek.getTime(), - end: firstDayOfThisWeek.getTime() - 1 - }; - } - case 'this_month': { - const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); - return { - start: firstDayOfMonth.getTime(), - end: now.getTime() - }; - } - case 'last_month': { - const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); - const firstDayOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1); - return { - start: firstDayOfLastMonth.getTime(), - end: firstDayOfThisMonth.getTime() - 1 - }; - } - case 'this_year': { - const firstDayOfYear = new Date(now.getFullYear(), 0, 1); - return { - start: firstDayOfYear.getTime(), - end: now.getTime() - }; - } - case 'last_year': { - const firstDayOfLastYear = new Date(now.getFullYear() - 1, 0, 1); - const firstDayOfThisYear = new Date(now.getFullYear(), 0, 1); - return { - start: firstDayOfLastYear.getTime(), - end: firstDayOfThisYear.getTime() - 1 - }; - } - case '12m': { - const twelveMonthsAgo = new Date(now); - twelveMonthsAgo.setMonth(twelveMonthsAgo.getMonth() - 12); - return { - start: twelveMonthsAgo.getTime(), - end: now.getTime() - }; - } - case 'all':{ - const start = new Date(app_creation_ts); - console.log('NARIMAN', start.getTime(), now.getTime()); - return { - start: start.getTime(), - end: now.getTime() - }; - } - default: - return null; + switch ( period ) { + case 'today': + return { + start: today.getTime(), + end: now.getTime(), + }; + case 'yesterday': { + const yesterday = new Date(today); + yesterday.setDate(yesterday.getDate() - 1); + return { + start: yesterday.getTime(), + end: today.getTime() - 1, + }; + } + case '7d': { + const weekAgo = new Date(now); + weekAgo.setDate(weekAgo.getDate() - 7); + return { + start: weekAgo.getTime(), + end: now.getTime(), + }; + } + case '30d': { + const monthAgo = new Date(now); + monthAgo.setDate(monthAgo.getDate() - 30); + return { + start: monthAgo.getTime(), + end: now.getTime(), + }; + } + case 'this_week': { + const firstDayOfWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); + return { + start: firstDayOfWeek.getTime(), + end: now.getTime(), + }; + } + case 'last_week': { + const firstDayOfLastWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay() - 7); + const firstDayOfThisWeek = new Date(now.getFullYear(), now.getMonth(), now.getDate() - now.getDay()); + return { + start: firstDayOfLastWeek.getTime(), + end: firstDayOfThisWeek.getTime() - 1, + }; + } + case 'this_month': { + const firstDayOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + return { + start: firstDayOfMonth.getTime(), + end: now.getTime(), + }; + } + case 'last_month': { + const firstDayOfLastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); + const firstDayOfThisMonth = new Date(now.getFullYear(), now.getMonth(), 1); + return { + start: firstDayOfLastMonth.getTime(), + end: firstDayOfThisMonth.getTime() - 1, + }; + } + case 'this_year': { + const firstDayOfYear = new Date(now.getFullYear(), 0, 1); + return { + start: firstDayOfYear.getTime(), + end: now.getTime(), + }; + } + case 'last_year': { + const firstDayOfLastYear = new Date(now.getFullYear() - 1, 0, 1); + const firstDayOfThisYear = new Date(now.getFullYear(), 0, 1); + return { + start: firstDayOfLastYear.getTime(), + end: firstDayOfThisYear.getTime() - 1, + }; + } + case '12m': { + const twelveMonthsAgo = new Date(now); + twelveMonthsAgo.setMonth(twelveMonthsAgo.getMonth() - 12); + return { + start: twelveMonthsAgo.getTime(), + end: now.getTime(), + }; + } + case 'all':{ + const start = new Date(app_creation_ts); + console.log('NARIMAN', start.getTime(), now.getTime()); + return { + start: start.getTime(), + end: now.getTime(), + }; + } + default: + return null; } }; const timeRange = getTimeRange(period); // Handle time-based grouping if stats_grouping is specified - if (stats_grouping) { + if ( stats_grouping ) { const timeFormat = this.mysqlDateFormats[stats_grouping]; - if (!timeFormat) { + if ( ! timeFormat ) { throw new Error(`Invalid stats_grouping: ${stats_grouping}. Supported values are: hour, day, week, month, year`); } // Generate all periods for the time range - const allPeriods = this.generateAllPeriods( - new Date(timeRange.start), - new Date(timeRange.end), - stats_grouping - ); + const allPeriods = this.generateAllPeriods(new Date(timeRange.start), + new Date(timeRange.end), + stats_grouping); - if (global.clickhouseClient) { + if ( global.clickhouseClient ) { const groupByFormat = this.clickhouseGroupByFormats[stats_grouping]; - const timeCondition = timeRange ? - `AND ts >= ${Math.floor(timeRange.start/1000)} AND ts < ${Math.floor(timeRange.end/1000)}` : ''; - + const timeCondition = timeRange ? + `AND ts >= ${Math.floor(timeRange.start / 1000)} AND ts < ${Math.floor(timeRange.end / 1000)}` : ''; + const [openResult, userResult] = await Promise.all([ global.clickhouseClient.query({ query: ` @@ -316,7 +313,7 @@ class AppInformationService extends BaseService { GROUP BY period ORDER BY period `, - format: 'JSONEachRow' + format: 'JSONEachRow', }), global.clickhouseClient.query({ query: ` @@ -329,63 +326,61 @@ class AppInformationService extends BaseService { GROUP BY period ORDER BY period `, - format: 'JSONEachRow' - }) + format: 'JSONEachRow', + }), ]); - + const openRows = await openResult.json(); const userRows = await userResult.json(); - + // Ensure counts are properly parsed as integers const processedOpenRows = openRows.map(row => ({ period: new Date(row.period), - count: parseInt(row.count) + count: parseInt(row.count), })); - + const processedUserRows = userRows.map(row => ({ period: new Date(row.period), - count: parseInt(row.count) + count: parseInt(row.count), })); - + // Calculate totals from the processed rows const totalOpenCount = processedOpenRows.reduce((sum, row) => sum + row.count, 0); const totalUserCount = processedUserRows.reduce((sum, row) => sum + row.count, 0); - + // Generate all periods and merge with actual data - const allPeriods = this.generateAllPeriods( - new Date(timeRange.start), - new Date(timeRange.end), - stats_grouping - ); - + const allPeriods = this.generateAllPeriods(new Date(timeRange.start), + new Date(timeRange.end), + stats_grouping); + const completeOpenStats = this.mergeWithGeneratedPeriods(processedOpenRows, allPeriods, stats_grouping); const completeUserStats = this.mergeWithGeneratedPeriods(processedUserRows, allPeriods, stats_grouping); - + return { open_count: totalOpenCount, user_count: totalUserCount, grouped_stats: { open_count: completeOpenStats, - user_count: completeUserStats + user_count: completeUserStats, }, - referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null + referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null, }; } - + else { // MySQL queries for grouped stats - const queryParams = timeRange ? - [app_uid, timeRange.start/1000, timeRange.end/1000] : + const queryParams = timeRange ? + [app_uid, timeRange.start / 1000, timeRange.end / 1000] : [app_uid]; const [openResult, userResult] = await Promise.all([ db.read(` - SELECT ` + + SELECT ${ db.case({ mysql: `DATE_FORMAT(FROM_UNIXTIME(ts/1000), '${timeFormat}') as period, `, sqlite: `STRFTIME('%Y-%m-%d %H', datetime(ts/1000, 'unixepoch'), '${timeFormat}') as period, `, - }) + - ` + }) + } COUNT(_id) as count FROM app_opens WHERE app_uid = ? @@ -394,19 +389,19 @@ class AppInformationService extends BaseService { ORDER BY period `, queryParams), db.read(` - SELECT ` + + SELECT ${ db.case({ mysql: `DATE_FORMAT(FROM_UNIXTIME(ts/1000), '${timeFormat}') as period, `, sqlite: `STRFTIME('%Y-%m-%d %H', datetime(ts/1000, 'unixepoch'), '${timeFormat}') as period, `, - }) + - ` + }) + } COUNT(DISTINCT user_id) as count FROM app_opens WHERE app_uid = ? ${timeRange ? 'AND ts >= ? AND ts < ?' : ''} GROUP BY period ORDER BY period - `, queryParams) + `, queryParams), ]); // Calculate totals @@ -416,11 +411,11 @@ class AppInformationService extends BaseService { // Convert MySQL results to the same format as needed const openRows = openResult.map(row => ({ period: row.period, - count: parseInt(row.count) + count: parseInt(row.count), })); const userRows = userResult.map(row => ({ period: row.period, - count: parseInt(row.count) + count: parseInt(row.count), })); // Merge with generated periods to include zero-value periods @@ -432,40 +427,40 @@ class AppInformationService extends BaseService { user_count: totalUserCount, grouped_stats: { open_count: completeOpenStats, - user_count: completeUserStats + user_count: completeUserStats, }, - referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null + referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null, }; } } // Handle non-grouped stats - if (global.clickhouseClient) { + if ( global.clickhouseClient ) { const openCountQuery = timeRange ? `SELECT COUNT(_id) AS open_count FROM app_opens WHERE app_uid = '${app_uid}' - AND ts >= ${Math.floor(timeRange.start/1000)} - AND ts < ${Math.floor(timeRange.end/1000)}` + AND ts >= ${Math.floor(timeRange.start / 1000)} + AND ts < ${Math.floor(timeRange.end / 1000)}` : `SELECT COUNT(_id) AS open_count FROM app_opens WHERE app_uid = '${app_uid}'`; const userCountQuery = timeRange ? `SELECT COUNT(DISTINCT user_id) AS uniqueUsers FROM app_opens WHERE app_uid = '${app_uid}' - AND ts >= ${Math.floor(timeRange.start/1000)} - AND ts < ${Math.floor(timeRange.end/1000)}` + AND ts >= ${Math.floor(timeRange.start / 1000)} + AND ts < ${Math.floor(timeRange.end / 1000)}` : `SELECT COUNT(DISTINCT user_id) AS uniqueUsers FROM app_opens WHERE app_uid = '${app_uid}'`; const [openResult, userResult] = await Promise.all([ global.clickhouseClient.query({ query: openCountQuery, - format: 'JSONEachRow' + format: 'JSONEachRow', }), global.clickhouseClient.query({ query: userCountQuery, - format: 'JSONEachRow' - }) + format: 'JSONEachRow', + }), ]); const openRows = await openResult.json(); @@ -474,16 +469,16 @@ class AppInformationService extends BaseService { const results = { open_count: parseInt(openRows[0].open_count), user_count: parseInt(userRows[0].uniqueUsers), - referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null + referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null, }; // Cache the results if period is 'all' - if (period === 'all') { + if ( period === 'all' ) { const key_open_count = `apps:open_count:uid:${app_uid}`; const key_user_count = `apps:user_count:uid:${app_uid}`; await Promise.all([ kv.set(key_open_count, results.open_count), - kv.set(key_user_count, results.user_count) + kv.set(key_user_count, results.user_count), ]); } @@ -494,7 +489,7 @@ class AppInformationService extends BaseService { const baseUserQuery = 'SELECT COUNT(DISTINCT user_id) AS user_count FROM app_opens WHERE app_uid = ?'; const generateQuery = (baseQuery, timeRange) => { - if (!timeRange) return baseQuery; + if ( ! timeRange ) return baseQuery; return `${baseQuery} AND ts >= ? AND ts < ?`; }; @@ -504,22 +499,22 @@ class AppInformationService extends BaseService { const [openResult, userResult] = await Promise.all([ db.read(openQuery, queryParams), - db.read(userQuery, queryParams) + db.read(userQuery, queryParams), ]); const results = { open_count: parseInt(openResult[0].open_count), user_count: parseInt(userResult[0].user_count), - referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null + referral_count: period === 'all' ? await kv.get(`apps:referral_count:uid:${app_uid}`) : null, }; // Cache the results if period is 'all' - if (period === 'all') { + if ( period === 'all' ) { const key_open_count = `apps:open_count:uid:${app_uid}`; const key_user_count = `apps:user_count:uid:${app_uid}`; await Promise.all([ kv.set(key_open_count, results.open_count), - kv.set(key_user_count, results.user_count) + kv.set(key_user_count, results.user_count), ]); } @@ -529,10 +524,10 @@ class AppInformationService extends BaseService { /** * Refreshes the application cache by querying the database for all apps and updating the key-value store. - * + * * @async * @returns {Promise} A promise that resolves when the cache refresh operation is complete. - * + * * @notes * - This method logs a tick event for performance monitoring. * - It populates the cache with app data indexed by name, id, and uid. @@ -544,13 +539,12 @@ class AppInformationService extends BaseService { let apps = await db.read('SELECT * FROM apps'); for ( const app of apps ) { - kv.set('apps:name:' + app.name, app); - kv.set('apps:id:' + app.id, app); - kv.set('apps:uid:' + app.uid, app); + kv.set(`apps:name:${ app.name}`, app); + kv.set(`apps:id:${ app.id}`, app); + kv.set(`apps:uid:${ app.uid}`, app); } } - /** * Refreshes the cache of app statistics including open and user counts. * @@ -571,35 +565,30 @@ class AppInformationService extends BaseService { // I'm not suggesting a specific solution for here, but it's // interesting to think about. - const apps = await db.read(`SELECT uid FROM apps`); + const apps = await db.read('SELECT uid FROM apps'); for ( const app of apps ) { const key_open_count = `apps:open_count:uid:${app.uid}`; - const { open_count } = (await db.read( - `SELECT COUNT(_id) AS open_count FROM app_opens WHERE app_uid = ?`, - [app.uid] - ))[0]; + const { open_count } = (await db.read('SELECT COUNT(_id) AS open_count FROM app_opens WHERE app_uid = ?', + [app.uid]))[0]; kv.set(key_open_count, open_count); const key_user_count = `apps:user_count:uid:${app.uid}`; - const { user_count } = (await db.read( - `SELECT COUNT(DISTINCT user_id) AS user_count FROM app_opens WHERE app_uid = ?`, - [app.uid] - ))[0]; + const { user_count } = (await db.read('SELECT COUNT(DISTINCT user_id) AS user_count FROM app_opens WHERE app_uid = ?', + [app.uid]))[0]; kv.set(key_user_count, user_count); } } - /** * Refreshes the cache of app referral statistics. - * + * * This method queries the database for user counts referred by each app's origin URL * and updates the cache with the referral counts for each app. - * + * * @notes * - This method logs a tick event for performance monitoring. - * + * * @async * @returns {Promise} A promise that resolves when the cache refresh operation is complete. */ @@ -608,7 +597,7 @@ class AppInformationService extends BaseService { const db = this.services.get('database').get(DB_READ, 'apps'); - const apps = await db.read(`SELECT uid, index_url FROM apps`); + const apps = await db.read('SELECT uid, index_url FROM apps'); for ( const app of apps ) { const origin = origin_from_url(app.index_url); @@ -627,10 +616,8 @@ class AppInformationService extends BaseService { } const key_referral_count = `apps:referral_count:uid:${app.uid}`; - const { referral_count } = (await db.read( - `SELECT COUNT(id) AS referral_count FROM user WHERE referrer LIKE ?`, - [origin + '%'] - ))[0]; + const { referral_count } = (await db.read('SELECT COUNT(id) AS referral_count FROM user WHERE referrer LIKE ?', + [`${origin }%`]))[0]; kv.set(key_referral_count, referral_count); } @@ -638,19 +625,18 @@ class AppInformationService extends BaseService { this.log.info('DONE refresh app stat referrals'); } - /** * Updates the cache with recently updated apps. - * + * * @description This method refreshes the cache containing the most recently updated applications. * It fetches all app UIDs, retrieves the corresponding app data, filters for approved apps, * sorts them by timestamp in descending order, and updates the 'recent' collection with * the UIDs of the top 50 most recent apps. - * + * * @returns {Promise} Resolves when the cache has been updated. */ async _refresh_recent_cache () { - const app_keys = kv.keys(`apps:uid:*`); + const app_keys = kv.keys('apps:uid:*'); let apps = []; for ( const key of app_keys ) { @@ -666,21 +652,20 @@ class AppInformationService extends BaseService { this.collections.recent = apps.map(app => app.uid).slice(0, 50); } - /** * Refreshes the cache of tags associated with apps. - * + * * This method iterates through all approved apps, extracts their tags, * and organizes them into a structured format for quick lookups. - * + * * This data is used by the `/query/app` router to facilitate tag-based * app discovery and categorization. - * + * * @async * @returns {Promise} */ async _refresh_tags () { - const app_keys = kv.keys(`apps:uid:*`); + const app_keys = kv.keys('apps:uid:*'); let apps = []; for ( const key of app_keys ) { @@ -713,17 +698,16 @@ class AppInformationService extends BaseService { this.tags = new_tags; } - /** * Deletes an application from the system. - * + * * This method performs the following actions: * - Retrieves the app data from cache or database if not provided. * - Deletes the app record from the database. * - Removes the app from all relevant caches (by name, id, and uid). * - Removes the app from the recent collection if present. * - Removes the app from any associated tags. - * + * * @param {string} app_uid - The unique identifier of the app to be deleted. * @param {Object} [app] - The app object, if already fetched. If not provided, it will be retrieved. * @throws {Error} If the app is not found in either cache or database. @@ -732,27 +716,23 @@ class AppInformationService extends BaseService { async delete_app (app_uid, app) { const db = this.services.get('database').get(DB_READ, 'apps'); - app = app ?? kv.get('apps:uid:' + app_uid); + app = app ?? kv.get(`apps:uid:${ app_uid}`); if ( ! app ) { - app = (await db.read( - `SELECT * FROM apps WHERE uid = ?`, - [app_uid] - ))[0]; + app = (await db.read('SELECT * FROM apps WHERE uid = ?', + [app_uid]))[0]; } if ( ! app ) { throw new Error('app not found'); } - await db.write( - `DELETE FROM apps WHERE uid = ? LIMIT 1`, - [app_uid] - ); + await db.write('DELETE FROM apps WHERE uid = ? LIMIT 1', + [app_uid]); // remove from caches - kv.del('apps:name:' + app.name); - kv.del('apps:id:' + app.id); - kv.del('apps:uid:' + app.uid); + kv.del(`apps:name:${ app.name}`); + kv.del(`apps:id:${ app.id}`); + kv.del(`apps:uid:${ app.uid}`); // remove from recent const index = this.collections.recent.indexOf(app_uid); @@ -775,42 +755,42 @@ class AppInformationService extends BaseService { } // Helper function to generate array of all periods between start and end dates - generateAllPeriods(startDate, endDate, grouping) { + generateAllPeriods (startDate, endDate, grouping) { const periods = []; let currentDate = new Date(startDate); - + // ???: In local debugging, `currentDate` evaluates to `Invalid Date`. // Does this work in prod? - - while (currentDate <= endDate) { + + while ( currentDate <= endDate ) { let period; - switch(grouping) { - case 'hour': - period = currentDate.toISOString().slice(0, 13) + ':00:00'; - currentDate.setHours(currentDate.getHours() + 1); - break; - case 'day': - period = currentDate.toISOString().slice(0, 10); - currentDate.setDate(currentDate.getDate() + 1); - break; - case 'week': - // Get the ISO week number - // TODO: Fix this use of `getWeekNumber`, which doesn't exist. - // I was not able to invoke this branch due to other - // blockers when testing locally so I'm leaving this as-is - // because I can't test for regressions. - const weekNum = String(getWeekNumber(currentDate)).padStart(2, '0'); - period = `${currentDate.getFullYear()}-${weekNum}`; - currentDate.setDate(currentDate.getDate() + 7); - break; - case 'month': - period = currentDate.toISOString().slice(0, 7); - currentDate.setMonth(currentDate.getMonth() + 1); - break; - case 'year': - period = currentDate.getFullYear().toString(); - currentDate.setFullYear(currentDate.getFullYear() + 1); - break; + switch ( grouping ) { + case 'hour': + period = `${currentDate.toISOString().slice(0, 13) }:00:00`; + currentDate.setHours(currentDate.getHours() + 1); + break; + case 'day': + period = currentDate.toISOString().slice(0, 10); + currentDate.setDate(currentDate.getDate() + 1); + break; + case 'week': + // Get the ISO week number + // TODO: Fix this use of `getWeekNumber`, which doesn't exist. + // I was not able to invoke this branch due to other + // blockers when testing locally so I'm leaving this as-is + // because I can't test for regressions. + const weekNum = String(getWeekNumber(currentDate)).padStart(2, '0'); + period = `${currentDate.getFullYear()}-${weekNum}`; + currentDate.setDate(currentDate.getDate() + 7); + break; + case 'month': + period = currentDate.toISOString().slice(0, 7); + currentDate.setMonth(currentDate.getMonth() + 1); + break; + case 'year': + period = currentDate.getFullYear().toString(); + currentDate.setFullYear(currentDate.getFullYear() + 1); + break; } periods.push({ period, count: 0 }); } @@ -818,43 +798,43 @@ class AppInformationService extends BaseService { } // Helper function to get ISO week number - getWeekNumber(date) { + getWeekNumber (date) { const target = new Date(date.valueOf()); const dayNumber = (date.getDay() + 6) % 7; target.setDate(target.getDate() - dayNumber + 3); const firstThursday = target.valueOf(); target.setMonth(0, 1); - if (target.getDay() !== 4) { + if ( target.getDay() !== 4 ) { target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7); } return 1 + Math.ceil((firstThursday - target) / 604800000); } // Helper function to merge actual data with generated periods - mergeWithGeneratedPeriods(actualData, allPeriods, stats_grouping) { + mergeWithGeneratedPeriods (actualData, allPeriods, stats_grouping) { // Create a map of period to count from actual data // First normalize the period format from both MySQL and ClickHouse const dataMap = new Map(actualData.map(item => { let period = item.period; // For ClickHouse results, convert the timestamp to match the expected format - if (item.period instanceof Date) { - switch(stats_grouping) { - case 'hour': - period = item.period.toISOString().slice(0, 13) + ':00:00'; - break; - case 'day': - period = item.period.toISOString().slice(0, 10); - break; - case 'week': - const weekNum = String(this.getWeekNumber(item.period)).padStart(2, '0'); - period = `${item.period.getFullYear()}-${weekNum}`; - break; - case 'month': - period = item.period.toISOString().slice(0, 7); - break; - case 'year': - period = item.period.getFullYear().toString(); - break; + if ( item.period instanceof Date ) { + switch ( stats_grouping ) { + case 'hour': + period = `${item.period.toISOString().slice(0, 13) }:00:00`; + break; + case 'day': + period = item.period.toISOString().slice(0, 10); + break; + case 'week': + const weekNum = String(this.getWeekNumber(item.period)).padStart(2, '0'); + period = `${item.period.getFullYear()}-${weekNum}`; + break; + case 'month': + period = item.period.toISOString().slice(0, 7); + break; + case 'year': + period = item.period.getFullYear().toString(); + break; } } return [period, parseInt(item.count)]; @@ -865,7 +845,7 @@ class AppInformationService extends BaseService { const count = dataMap.get(periodObj.period); return { period: periodObj.period, - count: count !== undefined ? count : 0 + count: count !== undefined ? count : 0, }; }); } diff --git a/src/backend/src/modules/apps/AppsModule.js b/src/backend/src/modules/apps/AppsModule.js index 5eb0e5d392..5cb742612b 100644 --- a/src/backend/src/modules/apps/AppsModule.js +++ b/src/backend/src/modules/apps/AppsModule.js @@ -1,23 +1,23 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class AppsModule extends AdvancedBase { async install (context) { @@ -41,5 +41,5 @@ class AppsModule extends AdvancedBase { } module.exports = { - AppsModule + AppsModule, }; diff --git a/src/backend/src/modules/apps/OldAppNameService.js b/src/backend/src/modules/apps/OldAppNameService.js index 5af9ec7152..ca52acab64 100644 --- a/src/backend/src/modules/apps/OldAppNameService.js +++ b/src/backend/src/modules/apps/OldAppNameService.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); -const { DB_READ } = require("../../services/database/consts"); +const BaseService = require('../../services/BaseService'); +const { DB_READ } = require('../../services/database/consts'); const N_MONTHS = 4; @@ -33,32 +33,27 @@ class OldAppNameService extends BaseService { const svc_event = this.services.get('event'); svc_event.on('app.rename', async (_, { app_uid, old_name }) => { this.log.info('GOT EVENT', { app_uid, old_name }); - await this.db.write( - 'INSERT INTO `old_app_names` (`app_uid`, `name`) VALUES (?, ?)', - [app_uid, old_name] - ); + await this.db.write('INSERT INTO `old_app_names` (`app_uid`, `name`) VALUES (?, ?)', + [app_uid, old_name]); }); } async check_app_name (name) { - const rows = await this.db.read( - 'SELECT * FROM `old_app_names` WHERE `name` = ?', - [name] - ); + const rows = await this.db.read('SELECT * FROM `old_app_names` WHERE `name` = ?', + [name]); if ( rows.length === 0 ) return; // Check if the app has been renamed in the last N months const [row] = rows; const timestamp = row.timestamp instanceof Date ? row.timestamp : new Date( - // Ensure timestamp ir processed as UTC - row.timestamp.endsWith('Z') ? row.timestamp : row.timestamp + 'Z' - ); + // Ensure timestamp ir processed as UTC + row.timestamp.endsWith('Z') ? row.timestamp : `${row.timestamp }Z`); const age = Date.now() - timestamp.getTime(); // const n_ms = 60 * 1000; - const n_ms = N_MONTHS * 30 * 24 * 60 * 60 * 1000 + const n_ms = N_MONTHS * 30 * 24 * 60 * 60 * 1000; this.log.info('AGE INFO', { input_time: row.timestamp, age, @@ -66,10 +61,8 @@ class OldAppNameService extends BaseService { }); if ( age > n_ms ) { // Remove record - await this.db.write( - 'DELETE FROM `old_app_names` WHERE `id` = ?', - [row.id] - ); + await this.db.write('DELETE FROM `old_app_names` WHERE `id` = ?', + [row.id]); // Return undefined return; } @@ -81,10 +74,8 @@ class OldAppNameService extends BaseService { } async remove_name (id) { - await this.db.write( - 'DELETE FROM `old_app_names` WHERE `id` = ?', - [id] - ); + await this.db.write('DELETE FROM `old_app_names` WHERE `id` = ?', + [id]); } } diff --git a/src/backend/src/modules/apps/ProtectedAppService.js b/src/backend/src/modules/apps/ProtectedAppService.js index 4a08991fa7..ccd6a1884f 100644 --- a/src/backend/src/modules/apps/ProtectedAppService.js +++ b/src/backend/src/modules/apps/ProtectedAppService.js @@ -17,12 +17,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { get_app } = require("../../helpers"); -const { UserActorType } = require("../../services/auth/Actor"); +const { get_app } = require('../../helpers'); +const { UserActorType } = require('../../services/auth/Actor'); const { PermissionImplicator, PermissionUtil, PermissionRewriter } = - require("../../services/auth/permissionUtils.mjs"); -const BaseService = require("../../services/BaseService"); - + require('../../services/auth/permissionUtils.mjs'); +const BaseService = require('../../services/BaseService'); /** * @class ProtectedAppService @@ -53,9 +52,7 @@ class ProtectedAppService extends BaseService { rewriter: async permission => { const [_1, name, ...rest] = PermissionUtil.split(permission); const app = await get_app({ name }); - return PermissionUtil.join( - _1, `uid#${app.uid}`, ...rest, - ); + return PermissionUtil.join(_1, `uid#${app.uid}`, ...rest); }, })); @@ -66,25 +63,25 @@ class ProtectedAppService extends BaseService { return permission.startsWith('app:'); }, checker: async ({ actor, permission }) => { - if ( !(actor.type instanceof UserActorType) ) { + if ( ! (actor.type instanceof UserActorType) ) { return undefined; } - + const parts = PermissionUtil.split(permission); if ( parts.length !== 3 ) return undefined; - + const [_, uid_part, lvl] = parts; if ( lvl !== 'access' ) return undefined; - + // track: slice a prefix const uid = uid_part.slice('uid#'.length); - + const app = await get_app({ uid }); if ( app.owner_user_id !== actor.type.user.id ) { return undefined; } - + return {}; }, })); diff --git a/src/backend/src/modules/apps/RecommendedAppsService.js b/src/backend/src/modules/apps/RecommendedAppsService.js index 6d08d7f4d3..3e072ea0c1 100644 --- a/src/backend/src/modules/apps/RecommendedAppsService.js +++ b/src/backend/src/modules/apps/RecommendedAppsService.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { get_app } = require("../../helpers"); -const BaseService = require("../../services/BaseService"); +const { get_app } = require('../../helpers'); +const BaseService = require('../../services/BaseService'); const get_apps = async ({ specifiers }) => { return await Promise.all(specifiers.map(async (specifier) => { @@ -73,13 +73,13 @@ class RecommendedAppsService extends BaseService { _construct () { this.app_names = new Set(RecommendedAppsService.APP_NAMES); } - + ['__on_boot.consolidation'] () { const svc_appIcon = this.services.get('app-icon'); const svc_event = this.services.get('event'); svc_event.on('apps.invalidate', (_, { app }) => { const sizes = svc_appIcon.get_sizes(); - + this.log.noticeme('Invalidating recommended apps', { app, sizes }); // If it's a single-app invalidation, only invalidate if the @@ -98,9 +98,8 @@ class RecommendedAppsService extends BaseService { } async get_recommended_apps ({ icon_size }) { - const recommended_cache_key = 'global:recommended-apps' + ( - icon_size ? `:icon-size:${icon_size}` : '' - ); + const recommended_cache_key = `global:recommended-apps${ + icon_size ? `:icon-size:${icon_size}` : ''}`; let recommended = kv.get(recommended_cache_key); if ( recommended ) return recommended; @@ -108,8 +107,8 @@ class RecommendedAppsService extends BaseService { // Prepare each app for returning to user by only returning the necessary fields // and adding them to the retobj array recommended = (await get_apps({ - specifiers: Array.from(this.app_names).map(name => ({ name })) - })).filter(app => !! app).map(app => { + specifiers: Array.from(this.app_names).map(name => ({ name })), + })).filter(app => !!app).map(app => { return { uuid: app.uid, name: app.name, @@ -120,7 +119,7 @@ class RecommendedAppsService extends BaseService { index_url: app.index_url, }; }); - + const svc_appIcon = this.services.get('app-icon'); // Iconify apps @@ -132,7 +131,7 @@ class RecommendedAppsService extends BaseService { } kv.set(recommended_cache_key, recommended); - + return recommended; } } diff --git a/src/backend/src/modules/apps/default-app-icon.js b/src/backend/src/modules/apps/default-app-icon.js index 2c938fca8a..43ede5d5eb 100644 --- a/src/backend/src/modules/apps/default-app-icon.js +++ b/src/backend/src/modules/apps/default-app-icon.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ diff --git a/src/backend/src/modules/broadcast/BroadcastModule.js b/src/backend/src/modules/broadcast/BroadcastModule.js index 96965534f8..ec2367aa21 100644 --- a/src/backend/src/modules/broadcast/BroadcastModule.js +++ b/src/backend/src/modules/broadcast/BroadcastModule.js @@ -1,23 +1,23 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class BroadcastModule extends AdvancedBase { async install (context) { diff --git a/src/backend/src/modules/broadcast/BroadcastService.js b/src/backend/src/modules/broadcast/BroadcastService.js index bd9bfdabe5..0cbdadafa7 100644 --- a/src/backend/src/modules/broadcast/BroadcastService.js +++ b/src/backend/src/modules/broadcast/BroadcastService.js @@ -16,23 +16,23 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); -const { CLink } = require("./connection/CLink"); -const { SLink } = require("./connection/SLink"); -const { Context } = require("../../util/context"); +const BaseService = require('../../services/BaseService'); +const { CLink } = require('./connection/CLink'); +const { SLink } = require('./connection/SLink'); +const { Context } = require('../../util/context'); class BroadcastService extends BaseService { static MODULES = { express: require('express'), // ['socket.io']: require('socket.io'), }; - + _construct () { this.peers_ = []; this.connections_ = []; this.trustedPublicKeys_ = {}; } - + async _init () { const peers = this.config.peers ?? []; for ( const peer_config of peers ) { @@ -45,16 +45,16 @@ class BroadcastService extends BaseService { this.peers_.push(peer); peer.connect(); } - + this._register_commands(this.services.get('commands')); - + const svc_event = this.services.get('event'); svc_event.on('outer.*', this.on_event.bind(this)); } - + async on_event (key, data, meta) { if ( meta.from_outside ) return; - + for ( const peer of this.peers_ ) { try { peer.send({ key, data, meta }); @@ -63,18 +63,18 @@ class BroadcastService extends BaseService { } } } - + async ['__on_install.websockets'] () { const svc_event = this.services.get('event'); const svc_webServer = this.services.get('web-server'); - + const server = svc_webServer.get_server(); const io = require('socket.io')(server, { cors: { origin: '*' }, path: '/wssinternal', }); - + io.on('connection', async socket => { const conn = new SLink({ keys: this.config.keys, @@ -82,17 +82,16 @@ class BroadcastService extends BaseService { socket, }); this.connections_.push(conn); - + conn.channels.message.on(({ key, data, meta }) => { if ( meta.from_outside ) { this.log.noticeme('possible over-sending'); return; } - + if ( key === 'test' ) { - this.log.noticeme(`test message: ` + - JSON.stringify(data) - ); + this.log.noticeme(`test message: ${ + JSON.stringify(data)}`); } meta.from_outside = true; @@ -103,7 +102,7 @@ class BroadcastService extends BaseService { }); }); } - + _register_commands (commands) { commands.registerCommands('broadcast', [ { @@ -112,10 +111,10 @@ class BroadcastService extends BaseService { handler: async (args, ctx) => { this.on_event('test', { contents: 'I am a test message', - }, {}) - } - } - ]) + }, {}); + }, + }, + ]); } } diff --git a/src/backend/src/modules/broadcast/connection/BaseLink.js b/src/backend/src/modules/broadcast/connection/BaseLink.js index a42db0c3e2..63eada97e1 100644 --- a/src/backend/src/modules/broadcast/connection/BaseLink.js +++ b/src/backend/src/modules/broadcast/connection/BaseLink.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { ChannelFeature } = require("../../../traits/ChannelFeature"); +const { AdvancedBase } = require('@heyputer/putility'); +const { ChannelFeature } = require('../../../traits/ChannelFeature'); class BaseLink extends AdvancedBase { static FEATURES = [ diff --git a/src/backend/src/modules/broadcast/connection/CLink.js b/src/backend/src/modules/broadcast/connection/CLink.js index 0788f90ae6..28233610eb 100644 --- a/src/backend/src/modules/broadcast/connection/CLink.js +++ b/src/backend/src/modules/broadcast/connection/CLink.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { BaseLink } = require("./BaseLink"); -const { KeyPairHelper } = require("./KeyPairHelper"); +const { BaseLink } = require('./BaseLink'); +const { KeyPairHelper } = require('./KeyPairHelper'); /** * Client-side link that establishes an encrypted socket.io connection. @@ -34,7 +34,7 @@ class CLink extends BaseLink { * Encrypts the data using AES-256-CBC and sends it through the socket. * The data is JSON stringified, encrypted with a random IV, and transmitted * as a buffer along with the IV. - * + * * @param {*} data - The data to be encrypted and sent through the socket * @returns {void} */ @@ -43,11 +43,9 @@ class CLink extends BaseLink { const require = this.require; const crypto = require('crypto'); const iv = crypto.randomBytes(16); - const cipher = crypto.createCipheriv( - 'aes-256-cbc', - this.aesKey, - iv, - ); + const cipher = crypto.createCipheriv('aes-256-cbc', + this.aesKey, + iv); const jsonified = JSON.stringify(data); let buffers = []; buffers.push(cipher.update(Buffer.from(jsonified, 'utf-8'))); @@ -96,11 +94,11 @@ class CLink extends BaseLink { extraHeaders: { ...(this.config.host ? { Host: this.config.host, - } : {}) - } + } : {}), + }, }); socket.on('connect', () => { - this.log.info(`connected`, { + this.log.info('connected', { address, }); @@ -115,19 +113,17 @@ class CLink extends BaseLink { socket.send({ $: 'take-my-key', key: this.keys.public, - message: kp_helper.write( - this.aesKey.toString('base64') - ), + message: kp_helper.write(this.aesKey.toString('base64')), }); this.state = this.constructor.ONLINE; }); socket.on('disconnect', () => { - this.log.info(`disconnected`, { + this.log.info('disconnected', { address, }); }); socket.on('connect_error', e => { - this.log.info(`connection error`, { + this.log.info('connection error', { address, message: e.message, }); diff --git a/src/backend/src/modules/broadcast/connection/KeyPairHelper.js b/src/backend/src/modules/broadcast/connection/KeyPairHelper.js index 2360b079b6..98b0cae1b8 100644 --- a/src/backend/src/modules/broadcast/connection/KeyPairHelper.js +++ b/src/backend/src/modules/broadcast/connection/KeyPairHelper.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -23,7 +23,7 @@ class KeyPairHelper extends AdvancedBase { static MODULES = { tweetnacl: require('tweetnacl'), }; - + constructor ({ kpublic, ksecret, @@ -33,17 +33,17 @@ class KeyPairHelper extends AdvancedBase { this.ksecret = ksecret; this.nonce_ = 0; } - + to_nacl_key_ (key) { console.log('WUT', key); const full_buffer = Buffer.from(key, 'base64'); // Remove version byte (assumed to be 0x31 and ignored for now) const buffer = full_buffer.slice(1); - + return new Uint8Array(buffer); } - + get naclSecret () { return this.naclSecret_ ?? ( this.naclSecret_ = this.to_nacl_key_(this.ksecret)); @@ -52,36 +52,31 @@ class KeyPairHelper extends AdvancedBase { return this.naclPublic_ ?? ( this.naclPublic_ = this.to_nacl_key_(this.kpublic)); } - + write (text) { const require = this.require; const nacl = require('tweetnacl'); const nonce = nacl.randomBytes(nacl.box.nonceLength); const message = {}; - + const textUint8 = new Uint8Array(Buffer.from(text, 'utf-8')); - const encryptedText = nacl.box( - textUint8, nonce, - this.naclPublic, this.naclSecret - ); + const encryptedText = nacl.box(textUint8, nonce, this.naclPublic, this.naclSecret); message.text = Buffer.from(encryptedText); message.nonce = Buffer.from(nonce); - + return message; } - + read (message) { const require = this.require; const nacl = require('tweetnacl'); - - const arr = nacl.box.open( - new Uint8Array(message.text), - new Uint8Array(message.nonce), - this.naclPublic, - this.naclSecret, - ); - + + const arr = nacl.box.open(new Uint8Array(message.text), + new Uint8Array(message.nonce), + this.naclPublic, + this.naclSecret); + return Buffer.from(arr).toString('utf-8'); } } diff --git a/src/backend/src/modules/broadcast/connection/SLink.js b/src/backend/src/modules/broadcast/connection/SLink.js index a915ed690f..dd8242da7f 100644 --- a/src/backend/src/modules/broadcast/connection/SLink.js +++ b/src/backend/src/modules/broadcast/connection/SLink.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { BaseLink } = require("./BaseLink"); -const { KeyPairHelper } = require("./KeyPairHelper"); +const { BaseLink } = require('./BaseLink'); +const { KeyPairHelper } = require('./KeyPairHelper'); class SLink extends BaseLink { static AUTHENTICATING = { @@ -29,14 +29,14 @@ class SLink extends BaseLink { } const trustedKeys = this.trustedKeys; - + const hasKey = trustedKeys[data.key]; if ( ! hasKey ) { this.disconnect(); return; } - - const is_trusted = trustedKeys.hasOwnProperty(data.key) + + const is_trusted = trustedKeys.hasOwnProperty(data.key); if ( ! is_trusted ) { this.disconnect(); return; @@ -46,38 +46,36 @@ class SLink extends BaseLink { kpublic: data.key, ksecret: this.keys.secret, }); - + const message = kp_helper.read(data.message); this.aesKey = Buffer.from(message, 'base64'); - + this.state = this.constructor.ONLINE; - } + }, }; static ONLINE = { on_message (data) { const require = this.require; const crypto = require('crypto'); - const decipher = crypto.createDecipheriv( - 'aes-256-cbc', - this.aesKey, - data.iv, - ) + const decipher = crypto.createDecipheriv('aes-256-cbc', + this.aesKey, + data.iv); const buffers = []; buffers.push(decipher.update(data.message)); buffers.push(decipher.final()); - + const rawjson = Buffer.concat(buffers).toString('utf-8'); - + const output = JSON.parse(rawjson); - + this.channels.message.emit(output); - } - } + }, + }; static OFFLINE = { on_message () { throw new Error('unexpected message'); - } - } + }, + }; _send () { // TODO: implement as a fallback diff --git a/src/backend/src/modules/captcha/CaptchaModule.js b/src/backend/src/modules/captcha/CaptchaModule.js index d91a64dca6..5acdcdbce7 100644 --- a/src/backend/src/modules/captcha/CaptchaModule.js +++ b/src/backend/src/modules/captcha/CaptchaModule.js @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); const CaptchaService = require('./services/CaptchaService'); /** @@ -30,14 +30,14 @@ const CaptchaService = require('./services/CaptchaService'); * that can be used to protect routes and determine captcha requirements. */ class CaptchaModule extends AdvancedBase { - async install(context) { - + async install (context) { + // Get services from context const services = context.get('services'); - + // Register the captcha service services.registerService('captcha', CaptchaService); } } -module.exports = { CaptchaModule }; \ No newline at end of file +module.exports = { CaptchaModule }; \ No newline at end of file diff --git a/src/backend/src/modules/captcha/middleware/captcha-middleware.js b/src/backend/src/modules/captcha/middleware/captcha-middleware.js index 2712383e7e..c75a83ec8b 100644 --- a/src/backend/src/modules/captcha/middleware/captcha-middleware.js +++ b/src/backend/src/modules/captcha/middleware/captcha-middleware.js @@ -17,14 +17,14 @@ * along with this program. If not, see . */ -const APIError = require("../../../api/APIError"); -const { Context } = require("../../../util/context"); +const APIError = require('../../../api/APIError'); +const { Context } = require('../../../util/context'); /** * Middleware that checks if captcha verification is required * This is the "first half" of the captcha verification process * It determines if verification is needed but doesn't perform verification - * + * * @param {Object} options - Configuration options * @param {boolean} [options.strictMode=true] - If true, fails closed on errors (more secure) * @returns {Function} Express middleware function @@ -32,7 +32,7 @@ const { Context } = require("../../../util/context"); const checkCaptcha = ({ svc_captcha }) => async (req, res, next) => { // Get services from the Context const services = Context.get('services'); - + if ( ! svc_captcha.enabled ) { req.captchaRequired = false; return next(); @@ -47,7 +47,7 @@ const checkCaptcha = ({ svc_captcha }) => async (req, res, next) => { required: true, }; await svc_event.emit('captcha.check', event); - + // Set captcha requirement based on service status req.captchaRequired = event.required; next(); @@ -57,7 +57,7 @@ const checkCaptcha = ({ svc_captcha }) => async (req, res, next) => { * Middleware that requires captcha verification * This is the "second half" of the captcha verification process * It uses the result from checkCaptcha to determine if verification is needed - * + * * @param {Object} options - Configuration options * @param {boolean} [options.strictMode=true] - If true, fails closed on errors (more secure) * @returns {Function} Express middleware function @@ -66,72 +66,72 @@ const requireCaptcha = (options = {}) => async (req, res, next) => { if ( ! req.captchaRequired ) { return next(); } - + const services = Context.get('services'); - + try { let captchaService; try { captchaService = services.get('captcha'); - } catch (error) { + } catch ( error ) { console.warn('Captcha verification: required service not available', error); return next(APIError.create('internal_error', null, { message: 'Captcha service unavailable', - status: 503 + status: 503, })); } // Fail closed if captcha service doesn't exist or isn't properly initialized - if (!captchaService || typeof captchaService.verifyCaptcha !== 'function') { + if ( !captchaService || typeof captchaService.verifyCaptcha !== 'function' ) { return next(APIError.create('internal_error', null, { message: 'Captcha service misconfigured', - status: 500 + status: 500, })); } - + // Check for captcha token and answer in request const captchaToken = req.body.captchaToken; const captchaAnswer = req.body.captchaAnswer; - if (!captchaToken || !captchaAnswer) { + if ( !captchaToken || !captchaAnswer ) { return next(APIError.create('captcha_required', null, { message: 'Captcha verification required', - status: 400 + status: 400, })); } - + // Verify the captcha let isValid; try { isValid = captchaService.verifyCaptcha(captchaToken, captchaAnswer); - } catch (verifyError) { + } catch ( verifyError ) { console.error('Captcha verification: threw an error', verifyError); return next(APIError.create('captcha_invalid', null, { message: 'Captcha verification failed', - status: 400 + status: 400, })); } - + // Check verification result - if (!isValid) { + if ( ! isValid ) { return next(APIError.create('captcha_invalid', null, { message: 'Invalid captcha response', - status: 400 + status: 400, })); } // Captcha verified successfully, continue next(); - } catch (error) { + } catch ( error ) { console.error('Captcha verification: unexpected error', error); return next(APIError.create('internal_error', null, { message: 'Captcha verification failed', - status: 500 + status: 500, })); } }; module.exports = { checkCaptcha, - requireCaptcha -}; \ No newline at end of file + requireCaptcha, +}; \ No newline at end of file diff --git a/src/backend/src/modules/captcha/services/CaptchaService.js b/src/backend/src/modules/captcha/services/CaptchaService.js index 3dac3cd1f3..1260dcda7d 100644 --- a/src/backend/src/modules/captcha/services/CaptchaService.js +++ b/src/backend/src/modules/captcha/services/CaptchaService.js @@ -33,30 +33,30 @@ class CaptchaService extends BaseService { /** * Initializes the captcha service with configuration and storage */ - async _construct() { + async _construct () { // Load dependencies this.crypto = require('crypto'); this.svgCaptcha = require('svg-captcha'); - + // In-memory token storage with expiration this.captchaTokens = new Map(); - + // Service instance diagnostic tracking this.serviceId = Math.random().toString(36).substring(2, 10); this.requestCounter = 0; - + // Get configuration from service config this.enabled = this.config.enabled === true; this.expirationTime = this.config.expirationTime || (10 * 60 * 1000); // 10 minutes default this.difficulty = this.config.difficulty || 'medium'; this.testMode = this.config.testMode === true; - + // Add a static test token for diagnostic purposes this.captchaTokens.set('test-static-token', { text: 'testanswer', - expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000) // 1 year + expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year }); - + // Flag to track if endpoints are registered this.endpointsRegistered = false; } @@ -69,17 +69,17 @@ class CaptchaService extends BaseService { /** * Sets up API endpoints and cleanup tasks */ - async _init() { - if (!this.enabled) { + async _init () { + if ( ! this.enabled ) { this.log.debug('Captcha service is disabled'); return; } // Set up periodic cleanup this.cleanupInterval = setInterval(() => this.cleanupExpiredTokens(), 15 * 60 * 1000); - + // Register endpoints if not already done - if (!this.endpointsRegistered) { + if ( ! this.endpointsRegistered ) { this.registerEndpoints(); this.endpointsRegistered = true; } @@ -88,8 +88,8 @@ class CaptchaService extends BaseService { /** * Cleanup method called when service is being destroyed */ - async _destroy() { - if (this.cleanupInterval) { + async _destroy () { + if ( this.cleanupInterval ) { clearInterval(this.cleanupInterval); } this.captchaTokens.clear(); @@ -99,36 +99,36 @@ class CaptchaService extends BaseService { * Registers the captcha API endpoints with the web service * @private */ - registerEndpoints() { - if (this.endpointsRegistered) { + registerEndpoints () { + if ( this.endpointsRegistered ) { return; } - + try { // Try to get the web service let webService = null; try { webService = this.services.get('web-service'); - } catch (error) { + } catch ( error ) { // Web service not available, try web-server try { webService = this.services.get('web-server'); - } catch (innerError) { + } catch ( innerError ) { this.log.warn('Neither web-service nor web-server are available yet'); return; } } - - if (!webService || !webService.app) { + + if ( !webService || !webService.app ) { this.log.warn('Web service found but app is not available'); return; } const app = webService.app; - + const api = this.require('express').Router(); app.use('/api/captcha', api); - + // Generate captcha endpoint Endpoint({ route: '/generate', @@ -137,7 +137,7 @@ class CaptchaService extends BaseService { const captcha = this.generateCaptcha(); res.json({ token: captcha.token, - image: captcha.data + image: captcha.data, }); }, }).attach(api); @@ -148,41 +148,41 @@ class CaptchaService extends BaseService { methods: ['POST'], handler: (req, res) => { const { token, answer } = req.body; - - if (!token || !answer) { - return res.status(400).json({ - valid: false, - error: 'Missing token or answer' + + if ( !token || !answer ) { + return res.status(400).json({ + valid: false, + error: 'Missing token or answer', }); } - + const isValid = this.verifyCaptcha(token, answer); res.json({ valid: isValid }); }, }).attach(api); - + // Special endpoint for automated testing // This should be disabled in production - if (this.testMode) { + if ( this.testMode ) { app.post('/api/captcha/create-test-token', (req, res) => { try { const { token, answer } = req.body; - - if (!token || !answer) { - return res.status(400).json({ - error: 'Missing token or answer' + + if ( !token || !answer ) { + return res.status(400).json({ + error: 'Missing token or answer', }); } - + // Store the test token with the provided answer this.captchaTokens.set(token, { text: answer.toLowerCase(), - expiresAt: Date.now() + this.expirationTime + expiresAt: Date.now() + this.expirationTime, }); - + this.log.debug(`Created test token: ${token} with answer: ${answer}`); res.json({ success: true }); - } catch (error) { + } catch ( error ) { this.log.error(`Error creating test token: ${error.message}`); res.status(500).json({ error: 'Failed to create test token' }); } @@ -204,15 +204,15 @@ class CaptchaService extends BaseService { requestCounter: this.requestCounter, hasStaticTestToken: this.captchaTokens.has('test-static-token'), tokensState: Array.from(this.captchaTokens).map(([token, data]) => ({ - tokenPrefix: token.substring(0, 8) + '...', + tokenPrefix: `${token.substring(0, 8) }...`, expiresAt: new Date(data.expiresAt).toISOString(), expired: data.expiresAt < Date.now(), - expectedAnswer: data.text - })) + expectedAnswer: data.text, + })), }; - + res.json(diagnosticInfo); - } catch (error) { + } catch ( error ) { this.log.error(`Error in diagnostic endpoint: ${error.message}`); res.status(500).json({ error: 'Diagnostic error' }); } @@ -224,16 +224,16 @@ class CaptchaService extends BaseService { // Check if we're the same service instance const currentTimestamp = Date.now(); const currentTokens = Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8)); - + // Create a test token that won't expire soon - const debugToken = 'debug-' + this.crypto.randomBytes(8).toString('hex'); + const debugToken = `debug-${ this.crypto.randomBytes(8).toString('hex')}`; const debugAnswer = 'test123'; - + this.captchaTokens.set(debugToken, { text: debugAnswer, - expiresAt: currentTimestamp + (60 * 60 * 1000) // 1 hour + expiresAt: currentTimestamp + (60 * 60 * 1000), // 1 hour }); - + // Information about the current service instance const serviceInfo = { message: 'Debug token created - use for testing captcha validation', @@ -244,11 +244,11 @@ class CaptchaService extends BaseService { tokensAfter: Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8)), currentTokenCount: this.captchaTokens.size, timestamp: currentTimestamp, - processId: process.pid + processId: process.pid, }; - + res.json(serviceInfo); - } catch (error) { + } catch ( error ) { this.log.error(`Error in debug-tokens endpoint: ${error.message}`); res.status(500).json({ error: 'Debug token creation error' }); } @@ -266,16 +266,16 @@ class CaptchaService extends BaseService { enabled: this.enabled, difficulty: this.difficulty, expirationTime: this.expirationTime, - testMode: this.testMode + testMode: this.testMode, }, usingCentralizedConfig: true, configConsistency: this.enabled === (this.enabled === true), serviceId: this.serviceId, - processId: process.pid + processId: process.pid, }; - + res.json(configInfo); - } catch (error) { + } catch ( error ) { this.log.error(`Error in config-status endpoint: ${error.message}`); res.status(500).json({ error: 'Configuration status error' }); } @@ -286,30 +286,30 @@ class CaptchaService extends BaseService { try { // Create a test captcha const testText = 'test123'; - const testToken = 'lifecycle-' + this.crypto.randomBytes(16).toString('hex'); - + const testToken = `lifecycle-${ this.crypto.randomBytes(16).toString('hex')}`; + // Store the test token this.captchaTokens.set(testToken, { text: testText, - expiresAt: Date.now() + this.expirationTime + expiresAt: Date.now() + this.expirationTime, }); - + // Verify the token exists const tokenExists = this.captchaTokens.has(testToken); // Try to verify with correct answer const correctVerification = this.verifyCaptcha(testToken, testText); // Check if token was deleted after verification const tokenAfterVerification = this.captchaTokens.has(testToken); - + // Create another test token - const testToken2 = 'lifecycle2-' + this.crypto.randomBytes(16).toString('hex'); - + const testToken2 = `lifecycle2-${ this.crypto.randomBytes(16).toString('hex')}`; + // Store the test token this.captchaTokens.set(testToken2, { text: testText, - expiresAt: Date.now() + this.expirationTime + expiresAt: Date.now() + this.expirationTime, }); - + res.json({ message: 'Token lifecycle test completed', serviceId: this.serviceId, @@ -319,9 +319,9 @@ class CaptchaService extends BaseService { verificationResult: correctVerification, tokenRemovedAfterVerification: !tokenAfterVerification, secondTokenCreated: this.captchaTokens.has(testToken2), - processId: process.pid + processId: process.pid, }); - } catch (error) { + } catch ( error ) { console.error('TOKENS_TRACKING: Error in test-lifecycle endpoint:', error); res.status(500).json({ error: 'Test lifecycle error' }); } @@ -329,17 +329,17 @@ class CaptchaService extends BaseService { this.endpointsRegistered = true; this.log.debug('Captcha service endpoints registered successfully'); - + // Emit an event that captcha service is ready try { const eventService = this.services.get('event'); - if (eventService) { + if ( eventService ) { eventService.emit('service-ready', 'captcha'); } - } catch (error) { + } catch ( error ) { // Ignore errors with event service } - } catch (error) { + } catch ( error ) { this.log.warn(`Could not register captcha endpoints: ${error.message}`); } } @@ -348,19 +348,19 @@ class CaptchaService extends BaseService { * Generates a new captcha with a unique token * @returns {Object} Object containing token and SVG image */ - generateCaptcha() { + generateCaptcha () { console.log('====== CAPTCHA GENERATION DIAGNOSTIC ======'); console.log('TOKENS_TRACKING: generateCaptcha called. Service ID:', this.serviceId); console.log('TOKENS_TRACKING: Token map size before generation:', this.captchaTokens.size); console.log('TOKENS_TRACKING: Static test token exists:', this.captchaTokens.has('test-static-token')); - + // Increment request counter for diagnostics this.requestCounter++; console.log('TOKENS_TRACKING: Request counter value:', this.requestCounter); - + console.log('generateCaptcha called, service enabled:', this.enabled); - - if (!this.enabled) { + + if ( ! this.enabled ) { console.log('Generation SKIPPED: Captcha service is disabled'); throw new Error('Captcha service is disabled'); } @@ -368,33 +368,33 @@ class CaptchaService extends BaseService { // Configure captcha options based on difficulty const options = this._getCaptchaOptions(); console.log('Using captcha options for difficulty:', this.difficulty); - + // Generate the captcha const captcha = this.svgCaptcha.create(options); console.log('Captcha created with text:', captcha.text); - + // Generate a unique token const token = this.crypto.randomBytes(32).toString('hex'); - console.log('Generated token:', token.substring(0, 8) + '...'); - + console.log('Generated token:', `${token.substring(0, 8) }...`); + // Store token with captcha text and expiration const expirationTime = Date.now() + this.expirationTime; console.log('Token will expire at:', new Date(expirationTime)); - + console.log('TOKENS_TRACKING: Token map size before storing new token:', this.captchaTokens.size); - + this.captchaTokens.set(token, { text: captcha.text.toLowerCase(), - expiresAt: expirationTime + expiresAt: expirationTime, }); - + console.log('TOKENS_TRACKING: Token map size after storing new token:', this.captchaTokens.size); console.log('Token stored in captchaTokens. Current token count:', this.captchaTokens.size); this.log.debug(`Generated captcha with token: ${token}`); - + return { token: token, - data: captcha.data + data: captcha.data, }; } @@ -404,22 +404,22 @@ class CaptchaService extends BaseService { * @param {string} userAnswer - The user's answer to verify * @returns {boolean} Whether the answer is valid */ - verifyCaptcha(token, userAnswer) { + verifyCaptcha (token, userAnswer) { console.log('====== CAPTCHA SERVICE VERIFICATION DIAGNOSTIC ======'); console.log('TOKENS_TRACKING: verifyCaptcha called. Service ID:', this.serviceId); console.log('TOKENS_TRACKING: Request counter during verification:', this.requestCounter); console.log('TOKENS_TRACKING: Static test token exists:', this.captchaTokens.has('test-static-token')); - console.log('TOKENS_TRACKING: Trying to verify token:', token ? token.substring(0, 8) + '...' : 'undefined'); - - console.log('verifyCaptcha called with token:', token ? token.substring(0, 8) + '...' : 'undefined'); + console.log('TOKENS_TRACKING: Trying to verify token:', token ? `${token.substring(0, 8) }...` : 'undefined'); + + console.log('verifyCaptcha called with token:', token ? `${token.substring(0, 8) }...` : 'undefined'); console.log('userAnswer:', userAnswer); console.log('Service enabled:', this.enabled); console.log('Number of tokens in captchaTokens:', this.captchaTokens.size); - + // Service health check this._checkServiceHealth(); - - if (!this.enabled) { + + if ( ! this.enabled ) { console.log('Verification SKIPPED: Captcha service is disabled'); this.log.warn('Captcha verification attempted while service is disabled'); throw new Error('Captcha service is disabled'); @@ -428,43 +428,43 @@ class CaptchaService extends BaseService { // Get captcha data for token const captchaData = this.captchaTokens.get(token); console.log('Captcha data found for token:', !!captchaData); - + // Invalid token or expired - if (!captchaData) { + if ( ! captchaData ) { console.log('Verification FAILED: No data found for this token'); - console.log('TOKENS_TRACKING: Available tokens (first 8 chars):', - Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8))); + console.log('TOKENS_TRACKING: Available tokens (first 8 chars):', + Array.from(this.captchaTokens.keys()).map(t => t.substring(0, 8))); this.log.debug(`Invalid captcha token: ${token}`); return false; } - - if (captchaData.expiresAt < Date.now()) { + + if ( captchaData.expiresAt < Date.now() ) { console.log('Verification FAILED: Token expired at:', new Date(captchaData.expiresAt)); this.log.debug(`Expired captcha token: ${token}`); return false; } - + // Normalize and compare answers const normalizedUserAnswer = userAnswer.toLowerCase().trim(); console.log('Expected answer:', captchaData.text); console.log('User answer (normalized):', normalizedUserAnswer); const isValid = captchaData.text === normalizedUserAnswer; console.log('Answer comparison result:', isValid); - + // Remove token after verification (one-time use) this.captchaTokens.delete(token); console.log('Token removed after verification (one-time use)'); console.log('TOKENS_TRACKING: Token map size after removing used token:', this.captchaTokens.size); - + this.log.debug(`Verified captcha token: ${token}, valid: ${isValid}`); return isValid; } - + /** * Simple diagnostic method to check service health * @private */ - _checkServiceHealth() { + _checkServiceHealth () { console.log('TOKENS_TRACKING: Service health check. ID:', this.serviceId, 'Token count:', this.captchaTokens.size); return true; } @@ -472,34 +472,34 @@ class CaptchaService extends BaseService { /** * Removes expired captcha tokens from memory */ - cleanupExpiredTokens() { + cleanupExpiredTokens () { console.log('TOKENS_TRACKING: Running token cleanup. Service ID:', this.serviceId); console.log('TOKENS_TRACKING: Token map size before cleanup:', this.captchaTokens.size); - + const now = Date.now(); let expiredCount = 0; let validCount = 0; - + // Log all tokens before cleanup console.log('TOKENS_TRACKING: Current tokens before cleanup:'); - for (const [token, data] of this.captchaTokens.entries()) { + for ( const [token, data] of this.captchaTokens.entries() ) { const isExpired = data.expiresAt < now; console.log(`TOKENS_TRACKING: Token ${token.substring(0, 8)}... expires: ${new Date(data.expiresAt).toISOString()}, expired: ${isExpired}`); - - if (isExpired) { + + if ( isExpired ) { expiredCount++; } else { validCount++; } } - + // Only do the actual cleanup if we found expired tokens - if (expiredCount > 0) { + if ( expiredCount > 0 ) { console.log(`TOKENS_TRACKING: Found ${expiredCount} expired tokens to remove and ${validCount} valid tokens to keep`); - + // Clean up expired tokens - for (const [token, data] of this.captchaTokens.entries()) { - if (data.expiresAt < now) { + for ( const [token, data] of this.captchaTokens.entries() ) { + if ( data.expiresAt < now ) { this.captchaTokens.delete(token); console.log(`TOKENS_TRACKING: Deleted expired token: ${token.substring(0, 8)}...`); } @@ -507,24 +507,24 @@ class CaptchaService extends BaseService { } else { console.log('TOKENS_TRACKING: No expired tokens found, skipping cleanup'); } - + // Skip cleanup for the static test token - if (this.captchaTokens.has('test-static-token')) { + if ( this.captchaTokens.has('test-static-token') ) { console.log('TOKENS_TRACKING: Static test token still exists after cleanup'); } else { console.log('TOKENS_TRACKING: WARNING - Static test token was removed during cleanup'); - + // Restore the static test token for diagnostic purposes this.captchaTokens.set('test-static-token', { text: 'testanswer', - expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000) // 1 year + expiresAt: Date.now() + (365 * 24 * 60 * 60 * 1000), // 1 year }); console.log('TOKENS_TRACKING: Restored static test token'); } - + console.log('TOKENS_TRACKING: Token map size after cleanup:', this.captchaTokens.size); - - if (expiredCount > 0) { + + if ( expiredCount > 0 ) { this.log.debug(`Cleaned up ${expiredCount} expired captcha tokens`); } } @@ -534,39 +534,39 @@ class CaptchaService extends BaseService { * @private * @returns {Object} Captcha configuration options */ - _getCaptchaOptions() { + _getCaptchaOptions () { const baseOptions = { size: 6, // Default captcha length ignoreChars: '0o1ilI', // Characters to avoid (confusing) noise: 2, // Lines to add as noise color: true, - background: '#f0f0f0' + background: '#f0f0f0', }; - - switch (this.difficulty) { - case 'easy': - return { - ...baseOptions, - size: 4, - width: 150, - height: 50, - noise: 1 - }; - case 'hard': - return { - ...baseOptions, - size: 7, - width: 200, - height: 60, - noise: 3 - }; - case 'medium': - default: - return { - ...baseOptions, - width: 180, - height: 50 - }; + + switch ( this.difficulty ) { + case 'easy': + return { + ...baseOptions, + size: 4, + width: 150, + height: 50, + noise: 1, + }; + case 'hard': + return { + ...baseOptions, + size: 7, + width: 200, + height: 60, + noise: 3, + }; + case 'medium': + default: + return { + ...baseOptions, + width: 180, + height: 50, + }; } } @@ -575,47 +575,47 @@ class CaptchaService extends BaseService { * This is used during initialization and can be called to check system status * @returns {boolean} Whether the service is properly configured and functioning */ - verifySelfTest() { + verifySelfTest () { try { // Ensure required dependencies are available - if (!this.svgCaptcha) { + if ( ! this.svgCaptcha ) { this.log.error('Captcha service self-test failed: svg-captcha module not available'); return false; } - - if (!this.enabled) { + + if ( ! this.enabled ) { this.log.warn('Captcha service self-test failed: service is disabled'); return false; } - + // Validate configuration - if (!this.expirationTime || typeof this.expirationTime !== 'number') { + if ( !this.expirationTime || typeof this.expirationTime !== 'number' ) { this.log.error('Captcha service self-test failed: invalid expiration time configuration'); return false; } - + // Basic functionality test - generate a test captcha and verify storage - const testToken = 'test-' + this.crypto.randomBytes(8).toString('hex'); + const testToken = `test-${ this.crypto.randomBytes(8).toString('hex')}`; const testText = 'testcaptcha'; - + // Store the test captcha this.captchaTokens.set(testToken, { text: testText, - expiresAt: Date.now() + this.expirationTime + expiresAt: Date.now() + this.expirationTime, }); - + // Verify the test captcha const correctVerification = this.verifyCaptcha(testToken, testText); - + // Check if verification worked and token was removed - if (!correctVerification || this.captchaTokens.has(testToken)) { + if ( !correctVerification || this.captchaTokens.has(testToken) ) { this.log.error('Captcha service self-test failed: verification test failed'); return false; } - + this.log.debug('Captcha service self-test passed'); return true; - } catch (error) { + } catch ( error ) { this.log.error(`Captcha service self-test failed with error: ${error.message}`); return false; } @@ -625,7 +625,7 @@ class CaptchaService extends BaseService { * Returns the service's diagnostic information * @returns {Object} Diagnostic information about the service */ - getDiagnosticInfo() { + getDiagnosticInfo () { return { serviceId: this.serviceId, enabled: this.enabled, @@ -635,14 +635,14 @@ class CaptchaService extends BaseService { enabled: this.enabled, difficulty: this.difficulty, expirationTime: this.expirationTime, - testMode: this.testMode + testMode: this.testMode, }, processId: process.pid, - testTokenExists: this.captchaTokens.has('test-static-token') + testTokenExists: this.captchaTokens.has('test-static-token'), }; } } // Export both as a named export and as a default export for compatibility module.exports = CaptchaService; -module.exports.CaptchaService = CaptchaService; \ No newline at end of file +module.exports.CaptchaService = CaptchaService; \ No newline at end of file diff --git a/src/backend/src/modules/core/AlarmService.d.ts b/src/backend/src/modules/core/AlarmService.d.ts index 1659cd7c8a..a838aaf08d 100644 --- a/src/backend/src/modules/core/AlarmService.d.ts +++ b/src/backend/src/modules/core/AlarmService.d.ts @@ -1,6 +1,6 @@ export class AlarmService { - create(id: string, message: string, fields?: object): void; - clear(id: string): void; - get_alarm(id: string): object | undefined; + create (id: string, message: string, fields?: object): void; + clear (id: string): void; + get_alarm (id: string): object | undefined; // Add more methods/properties as needed for MeteringService usage } \ No newline at end of file diff --git a/src/backend/src/modules/core/AlarmService.js b/src/backend/src/modules/core/AlarmService.js index d87350557a..4636801473 100644 --- a/src/backend/src/modules/core/AlarmService.js +++ b/src/backend/src/modules/core/AlarmService.js @@ -26,7 +26,6 @@ const fs = require('fs'); const BaseService = require('../../services/BaseService.js'); - /** * AlarmService class is responsible for managing alarms. * It provides methods for creating, clearing, and handling alarms. @@ -37,7 +36,7 @@ class AlarmService extends BaseService { identutil: 'core.util.identutil', stdioutil: 'core.util.stdioutil', Context: 'core.context', - } + }; /** * This method initializes the AlarmService by setting up its internal data structures and initializing any required dependencies. * @@ -72,17 +71,17 @@ class AlarmService extends BaseService { const lines = []; for ( const alarm of Object.values(this.alarms) ) { const line = - `\x1B[31;1m [alarm]\x1B[0m ` + + '\x1B[31;1m [alarm]\x1B[0m ' + `${alarm.id_string}: ${alarm.message} (${alarm.count})`; const line_lines = this.stdioutil.split_lines(line); lines.push(...line_lines); } return lines; - } + }; } } - + /** * AlarmService registers its commands at the consolidation phase because * the '_init' method of CommandService may not have been called yet. @@ -106,7 +105,7 @@ class AlarmService extends BaseService { * Method to create an alarm with the given ID, message, and fields. * If the ID already exists, it will be updated with the new fields * and the occurrence count will be incremented. - * + * * @param {string} id - Unique identifier for the alarm. * @param {string} message - Message associated with the alarm. * @param {object} fields - Additional information about the alarm. @@ -156,7 +155,7 @@ class AlarmService extends BaseService { */ get () { return alarm.timestamps?.length ?? 0; - } + }, }); Object.defineProperty(alarm, 'id_string', { @@ -173,10 +172,10 @@ class AlarmService extends BaseService { return alarm.id; } - const truncatedLongId = alarm.id.slice(0, 20) + '...'; + const truncatedLongId = `${alarm.id.slice(0, 20) }...`; return `${alarm.short_id} (${truncatedLongId})`; - } + }, }); return alarm; @@ -223,7 +222,7 @@ class AlarmService extends BaseService { */ clear (id) { const alarm = this.alarms[id]; - if ( !alarm ) { + if ( ! alarm ) { return; } delete this.alarms[id]; @@ -241,7 +240,7 @@ class AlarmService extends BaseService { } } return true; - } + }; const rule_actions = { 'no-alert': () => alarm.no_alert = true, @@ -257,12 +256,9 @@ class AlarmService extends BaseService { } } - handle_alarm_repeat_ (alarm) { - this.log.warn( - `REPEAT ${alarm.id_string} :: ${alarm.message} (${alarm.count})`, - alarm.fields, - ); + this.log.warn(`REPEAT ${alarm.id_string} :: ${alarm.message} (${alarm.count})`, + alarm.fields); this.apply_known_errors_(alarm); @@ -276,27 +272,25 @@ class AlarmService extends BaseService { } this.pager.alert({ - id: (alarm.id ?? 'something-bad') + '-r_${alarm.count}', + id: `${alarm.id ?? 'something-bad' }-r_\${alarm.count}`, message: alarm.message ?? alarm.id ?? 'something bad happened', source: 'alarm-service', severity, custom: { fields: fields_clean, trace: alarm.error?.stack, - } + }, }); } handle_alarm_on_ (alarm) { - this.log.error( - `ACTIVE ${alarm.id_string} :: ${alarm.message} (${alarm.count})`, - alarm.fields, - ); + this.log.error(`ACTIVE ${alarm.id_string} :: ${alarm.message} (${alarm.count})`, + alarm.fields); this.apply_known_errors_(alarm); // dev console - if ( this.global_config.env === 'dev' && ! this.attached_dev ) { + if ( this.global_config.env === 'dev' && !this.attached_dev ) { this.attached_dev = true; const svc_devConsole = this.services.get('dev-console'); svc_devConsole.turn_on_the_warning_lights(); @@ -329,7 +323,7 @@ class AlarmService extends BaseService { custom: { fields: fields_clean, trace: alarm.error?.stack, - } + }, }); // Write a .log file for the alert that happened @@ -347,7 +341,7 @@ class AlarmService extends BaseService { (async () => { try { - fs.appendFileSync(`alert_${alarm.id}.log`, alert_info + '\n'); + fs.appendFileSync(`alert_${alarm.id}.log`, `${alert_info }\n`); } catch (e) { this.log.error(`failed to write alert log: ${e.message}`); } @@ -358,17 +352,15 @@ class AlarmService extends BaseService { } handle_alarm_off_ (alarm) { - this.log.info( - `CLEAR ${alarm.id} :: ${alarm.message} (${alarm.count})`, - alarm.fields, - ); + this.log.info(`CLEAR ${alarm.id} :: ${alarm.message} (${alarm.count})`, + alarm.fields); } /** * Method to get an alarm by its ID. - * + * * @param {*} id - The ID of the alarm to get. - * @returns + * @returns */ get_alarm (id) { return this.alarms[id] ?? this.alarm_aliases[id]; @@ -380,7 +372,7 @@ class AlarmService extends BaseService { // This function is responsible for processing specific events related to alarms. // It can be used for tasks such as updating alarm status, sending notifications, or triggering actions. // This function is called internally by the AlarmService class. - + // /* // * handleAlarmEvent - Handles a specific alarm event. // * @@ -392,8 +384,10 @@ class AlarmService extends BaseService { // } const completeAlarmID = (args) => { // The alarm ID is the first argument, so return no results if we're on the second or later. - if (args.length > 1) + if ( args.length > 1 ) + { return; + } const lastArg = args[args.length - 1]; const results = []; @@ -416,7 +410,7 @@ class AlarmService extends BaseService { for ( const alarm of Object.values(this.alarms) ) { log.log(`${alarm.id_string}: ${alarm.message} (${alarm.count})`); } - } + }, }, { id: 'info', @@ -424,7 +418,7 @@ class AlarmService extends BaseService { handler: async (args, log) => { const [id] = args; const alarm = this.get_alarm(id); - if ( !alarm ) { + if ( ! alarm ) { log.log(`no alarm with id ${id}`); return; } @@ -451,10 +445,8 @@ class AlarmService extends BaseService { const [id] = args; const alarm = this.get_alarm(id); if ( ! alarm ) { - log.log( - `no alarm with id ${id}; ` + - `but calling clear(${JSON.stringify(id)}) anyway.` - ); + log.log(`no alarm with id ${id}; ` + + `but calling clear(${JSON.stringify(id)}) anyway.`); } this.clear(id); }, @@ -469,7 +461,7 @@ class AlarmService extends BaseService { for ( const alarm of alarms ) { this.handle_alarm_off_(alarm); } - } + }, }, { id: 'sound', @@ -477,7 +469,7 @@ class AlarmService extends BaseService { handler: async (args, log) => { const [id, message] = args; this.create(id ?? 'test', message, {}); - } + }, }, { id: 'inspect', @@ -485,18 +477,18 @@ class AlarmService extends BaseService { handler: async (args, log) => { const [id, occurance_idx] = args; const alarm = this.get_alarm(id); - if ( !alarm ) { + if ( ! alarm ) { log.log(`no alarm with id ${id}`); return; } const occurance = alarm.occurrences[occurance_idx]; - if ( !occurance ) { + if ( ! occurance ) { log.log(`no occurance with index ${occurance_idx}`); return; } log.log(`┏━━ Logs before: ${alarm.id_string} ━━━━`); for ( const lg of occurance.logs ) { - log.log("┃ " + this.logutil.stringify_log_entry(lg)); + log.log(`┃ ${ this.logutil.stringify_log_entry(lg)}`); } log.log(`┗━━ Logs before: ${alarm.id_string} ━━━━`); }, diff --git a/src/backend/src/modules/core/ContextService.js b/src/backend/src/modules/core/ContextService.js index 6304bc342d..2494bbcb2c 100644 --- a/src/backend/src/modules/core/ContextService.js +++ b/src/backend/src/modules/core/ContextService.js @@ -1,29 +1,29 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); -const { Context } = require("../../util/context"); +const BaseService = require('../../services/BaseService'); +const { Context } = require('../../util/context'); /** * ContextService provides a way for other services to register a hook to be * called when a context/subcontext is created. - * + * * Contexts are used to provide contextual information in the execution * context (dynamic scope). They can also be used to identify a "span"; * a span is a labelled frame of execution that can be used to track diff --git a/src/backend/src/modules/core/Core2Module.js b/src/backend/src/modules/core/Core2Module.js index ea5e688bad..d60bbdb309 100644 --- a/src/backend/src/modules/core/Core2Module.js +++ b/src/backend/src/modules/core/Core2Module.js @@ -1,28 +1,28 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); /** * A replacement for CoreModule with as few external relative requires as possible. * This will eventually be the successor to CoreModule, the main module for Puter's backend. - * + * * The scope of this module is: * - logging and error handling * - alarm handling @@ -40,36 +40,36 @@ class Core2Module extends AdvancedBase { for ( const k in lib ) { useapi.def(`core.${k}`, lib[k], { assign: true }); } - + useapi.def('core.context', require('../../util/context.js').Context); - + // === SERVICES === // const services = context.get('services'); const { LogService } = require('./LogService.js'); services.registerService('log-service', LogService); - - const { AlarmService } = require("./AlarmService.js"); + + const { AlarmService } = require('./AlarmService.js'); services.registerService('alarm', AlarmService); - - const { ErrorService } = require("./ErrorService.js"); + + const { ErrorService } = require('./ErrorService.js'); services.registerService('error-service', ErrorService); - - const { PagerService } = require("./PagerService.js"); + + const { PagerService } = require('./PagerService.js'); services.registerService('pager', PagerService); - - const { ExpectationService } = require("./ExpectationService.js"); + + const { ExpectationService } = require('./ExpectationService.js'); services.registerService('expectations', ExpectationService); - const { ProcessEventService } = require("./ProcessEventService.js"); + const { ProcessEventService } = require('./ProcessEventService.js'); services.registerService('process-event', ProcessEventService); - - const { ServerHealthService } = require("./ServerHealthService.js"); + + const { ServerHealthService } = require('./ServerHealthService.js'); services.registerService('server-health', ServerHealthService); - - const { ParameterService } = require("./ParameterService.js"); + + const { ParameterService } = require('./ParameterService.js'); services.registerService('params', ParameterService); - + const { ContextService } = require('./ContextService.js'); services.registerService('context', ContextService); } diff --git a/src/backend/src/modules/core/ErrorService.js b/src/backend/src/modules/core/ErrorService.js index c5ed96bc23..7b2f7a7fba 100644 --- a/src/backend/src/modules/core/ErrorService.js +++ b/src/backend/src/modules/core/ErrorService.js @@ -17,8 +17,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); - +const BaseService = require('../../services/BaseService'); /** * **ErrorContext Class** @@ -45,7 +44,6 @@ class ErrorContext { } } - /** * The ErrorService class is responsible for handling and reporting errors within the system. * It provides methods to initialize the service, create error contexts, and report errors with detailed logging and alarm mechanisms. @@ -67,22 +65,22 @@ class ErrorService extends BaseService { this.alarm = services.get('alarm'); this.backupLogger = services.get('log-service').create('error-service'); } - + /** * Creates an ErrorContext instance with the provided logging context. - * + * * @param {*} log_context The logging context to associate with the error reports. * @returns {ErrorContext} An ErrorContext instance. */ create (log_context) { return new ErrorContext(this, log_context); } - + /** * Reports an error with the specified location and details. * The "location" is a string up to the callers discretion to identify * the source of the error. - * + * * @param {*} location The location where the error occurred. * @param {*} fields The error details to report. * @param {boolean} [alarm=true] Whether to raise an alarm for the error. @@ -91,7 +89,7 @@ class ErrorService extends BaseService { report (location, { source, logger, trace, extra, message }, alarm = true) { message = message ?? source?.message; logger = logger ?? this.backupLogger; - logger.error(`Error @ ${location}: ${message}; ` + source?.stack); + logger.error(`Error @ ${location}: ${message}; ${ source?.stack}`); if ( alarm ) { const alarm_id = `${location}:${message}`; diff --git a/src/backend/src/modules/core/ExpectationService.js b/src/backend/src/modules/core/ExpectationService.js index ad192fe46b..795e177bd8 100644 --- a/src/backend/src/modules/core/ExpectationService.js +++ b/src/backend/src/modules/core/ExpectationService.js @@ -35,7 +35,7 @@ const BaseService = require('../../services/BaseService'); */ class ExpectationService extends BaseService { static USE = { - expect: 'core.expect' + expect: 'core.expect', }; /** @@ -62,14 +62,14 @@ class ExpectationService extends BaseService { handler: async (args, log) => { this.purgeExpectations_(); if ( this.expectations_.length < 1 ) { - log.log(`there are none`); + log.log('there are none'); return; } for ( const expectation of this.expectations_ ) { expectation.report(log); } - } - } + }, + }, ]); } @@ -91,14 +91,13 @@ class ExpectationService extends BaseService { * * @returns {void} */ - + // The comment should be placed above the method at line 68 setInterval(() => { this.purgeExpectations_(); }, 1000); } - /** * Purges expectations that have been met. * @@ -121,7 +120,7 @@ class ExpectationService extends BaseService { /** * Registers an expectation to be tracked by the service. - * + * * @param {Object} workUnit - The work unit to track * @param {string} checkpoint - The checkpoint to expect * @returns {void} @@ -131,8 +130,6 @@ class ExpectationService extends BaseService { } } - - module.exports = { - ExpectationService + ExpectationService, }; \ No newline at end of file diff --git a/src/backend/src/modules/core/LogService.js b/src/backend/src/modules/core/LogService.js index 80d630b050..c262118729 100644 --- a/src/backend/src/modules/core/LogService.js +++ b/src/backend/src/modules/core/LogService.js @@ -54,7 +54,6 @@ const display_log_level_label = { 100: 'ALL', }; - /** * Represents a logging context within the LogService. * This class is used to manage logging operations with specific context information, @@ -69,20 +68,28 @@ class LogContext { } sub (name, fields = {}) { - return new LogContext( - this.logService, - { - crumbs: name ? [...this.crumbs, name] : [...this.crumbs], - fields: {...this.fields, ...fields}, - } - ); + return new LogContext(this.logService, + { + crumbs: name ? [...this.crumbs, name] : [...this.crumbs], + fields: { ...this.fields, ...fields }, + }); } - info (message, fields, objects) { this.log(LOG_LEVEL_INFO, message, fields, objects); } - warn (message, fields, objects) { this.log(LOG_LEVEL_WARN, message, fields, objects); } - debug (message, fields, objects) { this.log(LOG_LEVEL_DEBU, message, fields, objects); } - error (message, fields, objects) { this.log(LOG_LEVEL_ERRO, message, fields, objects); } - tick (message, fields, objects) { this.log(LOG_LEVEL_TICK, message, fields, objects); } + info (message, fields, objects) { + this.log(LOG_LEVEL_INFO, message, fields, objects); + } + warn (message, fields, objects) { + this.log(LOG_LEVEL_WARN, message, fields, objects); + } + debug (message, fields, objects) { + this.log(LOG_LEVEL_DEBU, message, fields, objects); + } + error (message, fields, objects) { + this.log(LOG_LEVEL_ERRO, message, fields, objects); + } + tick (message, fields, objects) { + this.log(LOG_LEVEL_TICK, message, fields, objects); + } called (fields = {}) { this.log(LOG_LEVEL_DEBU, 'called', fields); } @@ -94,11 +101,9 @@ class LogContext { } cache (isCacheHit, identifier, fields = {}) { - this.log( - LOG_LEVEL_DEBU, - isCacheHit ? 'cache_hit' : 'cache_miss', - { identifier, ...fields }, - ); + this.log(LOG_LEVEL_DEBU, + isCacheHit ? 'cache_hit' : 'cache_miss', + { identifier, ...fields }); } log (log_level, message, fields = {}, objects = {}) { @@ -108,7 +113,7 @@ class LogContext { if ( x && x.get('trace_request') ) { fields.trace_request = x.get('trace_request'); } - if ( ! fields.actor && x && x.get('actor') ) { + if ( !fields.actor && x && x.get('actor') ) { try { fields.actor = x.get('actor'); } catch (e) { @@ -124,20 +129,19 @@ class LogContext { } if ( Context.get('injected_logger', { allow_fallback: true }) ) { Context.get('injected_logger').log( - message + (fields ? ('; fields: ' + JSON.stringify(fields)) : ''), - ); + message + (fields ? (`; fields: ${ JSON.stringify(fields)}`) : '')); } - this.logService.log_( - log_level, - this.crumbs, - message, fields, objects, - ); + this.logService.log_(log_level, + this.crumbs, + message, + fields, + objects); } /** * Generates a human-readable trace ID for logging purposes. - * - * @returns {string} A trace ID in the format 'xxxxxx-xxxxxx' where each segment is a + * + * @returns {string} A trace ID in the format 'xxxxxx-xxxxxx' where each segment is a * random string of six lowercase letters and digits. */ mkid () { @@ -158,7 +162,6 @@ class LogContext { return this; } - /** * Gets the log buffer maintained by the LogService. This shows the most * recent log entries. @@ -176,10 +179,10 @@ class LogContext { /** * @class DevLogger * @classdesc -* A development logger class designed for logging messages during development. -* This logger can either log directly to console or delegate logging to another logger. +* A development logger class designed for logging messages during development. +* This logger can either log directly to console or delegate logging to another logger. * It provides functionality to turn logging on/off, and can optionally write logs to a file. -* +* * @param {function} log - The logging function, typically `console.log` or similar. * @param {object} [opt_delegate] - An optional logger to which log messages can be delegated. */ @@ -196,35 +199,36 @@ class DevLogger { } onLogMessage (log_lvl, crumbs, message, fields, objects) { if ( this.delegate ) { - this.delegate.onLogMessage( - log_lvl, crumbs, message, fields, objects, - ); + this.delegate.onLogMessage(log_lvl, crumbs, message, fields, objects); } if ( this.off ) return; - - if ( ! process.env.DEBUG && log_lvl.ordinal > display_log_level ) return; - const ld = Context.get('logdent', { allow_fallback: true }) + if ( !process.env.DEBUG && log_lvl.ordinal > display_log_level ) return; + + const ld = Context.get('logdent', { allow_fallback: true }); const prefix = globalThis.dev_console_indent_on ? Array(ld ?? 0).fill(' ').join('') : ''; this.log_(stringify_log_entry({ prefix, - log_lvl, crumbs, message, fields, objects, + log_lvl, + crumbs, + message, + fields, + objects, })); } - + log_ (text) { if ( this.recto ) { const fs = require('node:fs'); - fs.appendFileSync(this.recto, text + '\n'); + fs.appendFileSync(this.recto, `${text }\n`); } this.log(text); } } - /** * @class NullLogger * @description A logger that does nothing, effectively disabling logging. @@ -244,10 +248,9 @@ class NullLogger { } } - /** * WinstonLogger Class -* +* * A logger that delegates log messages to a Winston logger instance. */ class WinstonLogger { @@ -264,13 +267,12 @@ class WinstonLogger { } } - /** * @class TimestampLogger * @classdesc A logger that adds timestamps to log messages before delegating them to another logger. * This class wraps another logger instance to ensure that all log messages include a timestamp, * which can be useful for tracking the sequence of events in a system. -* +* * @param {Object} delegate - The logger instance to which the timestamped log messages are forwarded. */ class TimestampLogger { @@ -283,7 +285,6 @@ class TimestampLogger { } } - /** * The `BufferLogger` class extends the logging functionality by maintaining a buffer of log entries. * This class is designed to: @@ -307,7 +308,6 @@ class BufferLogger { } } - /** * Represents a custom logger that can modify log messages before they are passed to another logger. * @class CustomLogger @@ -331,25 +331,27 @@ class CustomLogger { try { ret = await this.callback({ context, - log_lvl, crumbs, message, fields, args: a, + log_lvl, + crumbs, + message, + fields, + args: a, }); } catch (e) { console.error('error?', e); } - + if ( ret && ret.skip ) return; if ( ! ret ) { - this.delegate.onLogMessage( - log_lvl, - crumbs, - message, - fields, - ...a, - ); + this.delegate.onLogMessage(log_lvl, + crumbs, + message, + fields, + ...a); return; } - + const { log_lvl: _log_lvl, crumbs: _crumbs, @@ -358,27 +360,24 @@ class CustomLogger { args, } = ret; - this.delegate.onLogMessage( - _log_lvl ?? log_lvl, - _crumbs ?? crumbs, - _message ?? message, - _fields ?? fields, - ...(args ?? a ?? []), - ); + this.delegate.onLogMessage(_log_lvl ?? log_lvl, + _crumbs ?? crumbs, + _message ?? message, + _fields ?? fields, + ...(args ?? a ?? [])); } } - /** -* The `LogService` class extends `BaseService` and is responsible for managing and -* orchestrating various logging functionalities within the application. It handles -* log initialization, middleware registration, log directory management, and +* The `LogService` class extends `BaseService` and is responsible for managing and +* orchestrating various logging functionalities within the application. It handles +* log initialization, middleware registration, log directory management, and * provides methods for creating log contexts and managing log output levels. */ class LogService extends BaseService { static MODULES = { path: require('path'), - } + }; /** * Defines the modules required by the LogService class. * This static property contains modules that are used for file path operations. @@ -389,7 +388,7 @@ class LogService extends BaseService { this.loggers = []; this.bufferLogger = null; } - + /** * Registers a custom logging middleware with the LogService. * @param {*} callback - The callback function that modifies log parameters before delegation. @@ -397,7 +396,7 @@ class LogService extends BaseService { register_log_middleware (callback) { this.loggers[0] = new CustomLogger(this.loggers[0], callback); } - + /** * Registers logging commands with the command service. */ @@ -408,20 +407,20 @@ class LogService extends BaseService { id: 'show', description: 'toggle log output', handler: async (args, log) => { - this.devlogger && (this.devlogger.off = ! this.devlogger.off); - } + this.devlogger && (this.devlogger.off = !this.devlogger.off); + }, }, { id: 'rec', description: 'start recording to a file via dev logger', handler: async (args, ctx) => { const [name] = args; - const {log} = ctx; + const { log } = ctx; if ( ! this.devlogger ) { log('no dev logger; what are you doing?'); } this.devlogger.recto = name; - } + }, }, { id: 'stop', @@ -431,15 +430,15 @@ class LogService extends BaseService { log('no dev logger; what are you doing?'); } this.devlogger.recto = null; - } + }, }, { id: 'indent', description: 'toggle log indentation', handler: async (args, log) => { globalThis.dev_console_indent_on = - ! globalThis.dev_console_indent_on; - } + !globalThis.dev_console_indent_on; + }, }, { id: 'get-level', @@ -460,11 +459,11 @@ class LogService extends BaseService { } /** * Registers logging commands with the command service. - * + * * This method sets up various logging commands that can be used to * interact with the log output, such as toggling log display, * starting/stopping log recording, and toggling log indentation. - * + * * @memberof LogService */ async _init () { @@ -475,8 +474,8 @@ class LogService extends BaseService { let logger; if ( ! config.no_winston ) - logger = new WinstonLogger( - winston.createLogger({ + { + logger = new WinstonLogger(winston.createLogger({ levels: WINSTON_LEVELS, transports: [ new winston.transports.DailyRotateFile({ @@ -485,8 +484,8 @@ class LogService extends BaseService { zippedArchive: true, maxSize: '20m', - // TODO: uncomment when we have a log backup strategy - // maxFiles: '14d', + // TODO: uncomment when we have a log backup strategy + // maxFiles: '14d', }), new winston.transports.DailyRotateFile({ level: 'error', @@ -495,8 +494,8 @@ class LogService extends BaseService { zippedArchive: true, maxSize: '20m', - // TODO: uncomment when we have a log backup strategy - // maxFiles: '14d', + // TODO: uncomment when we have a log backup strategy + // maxFiles: '14d', }), new winston.transports.DailyRotateFile({ level: 'system', @@ -505,18 +504,18 @@ class LogService extends BaseService { zippedArchive: true, maxSize: '20m', - // TODO: uncomment when we have a log backup strategy - // maxFiles: '14d', + // TODO: uncomment when we have a log backup strategy + // maxFiles: '14d', }), ], - }), - ); + })); + } if ( config.env === 'dev' ) { logger = config.flag_no_logs // useful for profiling ? new NullLogger() : new DevLogger(console.log.bind(console), logger); - + this.devlogger = logger; } @@ -565,10 +564,10 @@ class LogService extends BaseService { if ( ! globalThis.original_console_object ) { globalThis.original_console_object = console; } - + // Keep console prototype const logconsole = Object.create(console); - + // Override simple log functions const logfn = level => (...a) => { logger[level](a.map(arg => { @@ -579,26 +578,24 @@ class LogService extends BaseService { logconsole.log = logfn('info'); logconsole.warn = logfn('warn'); logconsole.error = logfn('error'); - + globalThis.console = logconsole; } } /** * Create a new log context with the specified prefix - * + * * @param {1} prefix - The prefix for the log context * @param {*} fields - Optional fields to include in the log context * @returns {LogContext} A new log context with the specified prefix and fields */ create (prefix, fields = {}) { - const logContext = new LogContext( - this, - { - crumbs: [prefix], - fields, - }, - ); + const logContext = new LogContext(this, + { + crumbs: [prefix], + fields, + }); return logContext; } @@ -607,15 +604,13 @@ class LogService extends BaseService { try { // skip messages that are above the output level if ( log_lvl.ordinal > this.output_lvl ) return; - + if ( this.config.trace_logs ) { fields.stack = (new Error('logstack')).stack; } for ( const logger of this.loggers ) { - logger.onLogMessage( - log_lvl, crumbs, message, fields, objects, - ); + logger.onLogMessage(log_lvl, crumbs, message, fields, objects); } } catch (e) { // If logging fails, we don't want anything to happen @@ -629,13 +624,12 @@ class LogService extends BaseService { } } - /** * Ensures that a log directory exists for logging purposes. * This method attempts to create or locate a directory for log files, * falling back through several predefined paths if the preferred * directory does not exist or cannot be created. - * + * * @throws {Error} If no suitable log directory can be found or created. */ ensure_log_directory_ () { @@ -689,7 +683,7 @@ class LogService extends BaseService { /** * Generates a sanitized file path for log files. - * + * * @param {string} name - The name of the log file, which will be sanitized to remove any path characters. * @returns {string} A sanitized file path within the log directory. */ @@ -699,11 +693,10 @@ class LogService extends BaseService { return this.modules.path.join(this.log_directory, name); } - /** * Get the most recent log entries from the buffer maintained by the LogService. * By default, the buffer contains the last 20 log entries. - * @returns + * @returns */ get_log_buffer () { return this.bufferLogger.buffer; @@ -712,5 +705,5 @@ class LogService extends BaseService { module.exports = { LogService, - stringify_log_entry + stringify_log_entry, }; \ No newline at end of file diff --git a/src/backend/src/modules/core/PagerService.js b/src/backend/src/modules/core/PagerService.js index 99733805c8..7b24dded24 100644 --- a/src/backend/src/modules/core/PagerService.js +++ b/src/backend/src/modules/core/PagerService.js @@ -21,7 +21,6 @@ const pdjs = require('@pagerduty/pdjs'); const BaseService = require('../../services/BaseService'); const util = require('util'); - /** * @class PagerService * @extends BaseService @@ -34,14 +33,14 @@ const util = require('util'); class PagerService extends BaseService { static USE = { Context: 'core.context', - } - + }; + async _construct () { this.config = this.global_config.pager; this.alertHandlers_ = []; } - + /** * PagerService registers its commands at the consolidation phase because * the '_init' method of CommandService may not have been called yet. @@ -123,7 +122,6 @@ class PagerService extends BaseService { } } - /** * Sends an alert to all registered alert handlers. * @@ -155,9 +153,9 @@ class PagerService extends BaseService { source: 'test', severity, }); - } - } - ]) + }, + }, + ]); } } diff --git a/src/backend/src/modules/core/ParameterService.js b/src/backend/src/modules/core/ParameterService.js index cbd9d53649..f011cd81f4 100644 --- a/src/backend/src/modules/core/ParameterService.js +++ b/src/backend/src/modules/core/ParameterService.js @@ -1,24 +1,24 @@ // METADATA // {"ai-commented":{"service":"claude"}} /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); /** * @class ParameterService @@ -33,7 +33,6 @@ class ParameterService extends BaseService { /** @type {Array} */ this.parameters_ = []; } - /** * Initializes the service by registering commands with the command service. @@ -45,31 +44,28 @@ class ParameterService extends BaseService { this._registerCommands(this.services.get('commands')); } - createParameters(serviceName, parameters, opt_instance) { - for (const parameter of parameters) { + createParameters (serviceName, parameters, opt_instance) { + for ( const parameter of parameters ) { this.log.debug(`registering parameter ${serviceName}:${parameter.id}`); this.parameters_.push(new Parameter({ ...parameter, id: `${serviceName}:${parameter.id}`, })); if ( opt_instance ) { - this.bindToInstance( - `${serviceName}:${parameter.id}`, - opt_instance, - parameter.id, - ); + this.bindToInstance(`${serviceName}:${parameter.id}`, + opt_instance, + parameter.id); } } } - /** * Gets the value of a parameter by its ID * @param {string} id - The unique identifier of the parameter to retrieve * @returns {Promise<*>} The current value of the parameter * @throws {Error} If parameter with given ID is not found */ - async get(id) { + async get (id) { const parameter = this._get_param(id); return await parameter.get(); } @@ -84,7 +80,7 @@ class ParameterService extends BaseService { return parameter.subscribe(listener); } - _get_param(id) { + _get_param (id) { const parameter = this.parameters_.find(p => p.spec_.id === id); if ( ! parameter ) { throw new Error(`unknown parameter: ${id}`); @@ -99,8 +95,10 @@ class ParameterService extends BaseService { _registerCommands (commands) { const completeParameterName = (args) => { // The parameter name is the first argument, so return no results if we're on the second or later. - if (args.length > 1) + if ( args.length > 1 ) + { return; + } const lastArg = args[args.length - 1]; return this.parameters_ @@ -143,20 +141,19 @@ class ParameterService extends BaseService { log.log(`available parameters${ prefix ? ` (starting with: ${prefix})` : '' }:`); - for (const parameter of parameters) { + for ( const parameter of parameters ) { // log.log(`- ${parameter.spec_.id}: ${parameter.spec_.description}`); // Log parameter description and value const value = await parameter.get(); log.log(`- ${parameter.spec_.id} = ${value}`); log.log(` ${parameter.spec_.description}`); } - } - } + }, + }, ]); } } - /** * @class Parameter * @description Represents a configurable parameter with value management, constraints, and change notification capabilities. @@ -164,7 +161,7 @@ class ParameterService extends BaseService { * Supports validation through configurable constraints and maintains a list of value change listeners. */ class Parameter { - constructor(spec) { + constructor (spec) { this.spec_ = spec; this.valueListeners_ = []; @@ -173,7 +170,6 @@ class Parameter { } } - /** * Sets a new value for the parameter after validating against constraints * @param {*} value - The new value to set for the parameter @@ -195,7 +191,6 @@ class Parameter { } } - /** * Gets the current value of this parameter * @returns {Promise<*>} The parameter's current value diff --git a/src/backend/src/modules/core/ProcessEventService.js b/src/backend/src/modules/core/ProcessEventService.js index 7a55568056..adac88f376 100644 --- a/src/backend/src/modules/core/ProcessEventService.js +++ b/src/backend/src/modules/core/ProcessEventService.js @@ -18,21 +18,21 @@ * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); /** * Service class that handles process-wide events and errors. * Provides centralized error handling for uncaught exceptions and unhandled promise rejections. * Sets up event listeners on the process object to capture and report critical errors * through the logging and error reporting services. -* +* * @class ProcessEventService */ class ProcessEventService extends BaseService { static USE = { Context: 'core.context', }; - + _init () { const services = this.services; const log = services.get('log-service').create('process-event-service'); @@ -44,7 +44,7 @@ class ProcessEventService extends BaseService { * Sets up an event listener that reports errors when uncaught exceptions occur * @param {Error} err - The uncaught exception error object * @param {string} origin - The origin of the uncaught exception - * @returns {Promise} + * @returns {Promise} */ await this.Context.allow_fallback(async () => { errors.report('process:uncaughtException', { diff --git a/src/backend/src/modules/core/ServerHealthService.js b/src/backend/src/modules/core/ServerHealthService.js index 7846e48aa3..0e1717fe13 100644 --- a/src/backend/src/modules/core/ServerHealthService.js +++ b/src/backend/src/modules/core/ServerHealthService.js @@ -17,9 +17,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); -const { time, promise } = require("@heyputer/putility").libs; - +const BaseService = require('../../services/BaseService'); +const { time, promise } = require('@heyputer/putility').libs; /** * The ServerHealthService class provides comprehensive health monitoring for the server. @@ -28,15 +27,15 @@ const { time, promise } = require("@heyputer/putility").libs; * - Managing health check results and failures * - Triggering alarms for critical conditions * - Logging and managing statistics for health metrics -* +* * This service is designed to work primarily on Linux systems, reading system metrics * from `/proc/meminfo` and handling alarms via an external 'alarm' service. */ class ServerHealthService extends BaseService { static USE = { - linuxutil: 'core.util.linuxutil' + linuxutil: 'core.util.linuxutil', }; - + /** * Defines the modules used by ServerHealthService. * This static property is used to initialize and access system modules required for health checks. @@ -45,12 +44,12 @@ class ServerHealthService extends BaseService { */ static MODULES = { fs: require('fs'), - } + }; /** * Initializes the internal checks and failure tracking for the service. * This method sets up empty arrays to store health checks and their failure statuses. - * + * * @private */ _construct () { @@ -73,7 +72,6 @@ class ServerHealthService extends BaseService { `/proc/meminfo` directly. */ - const min_available_KiB = 1024 * 1024 * 2; // 2 GiB const svc_alarm = this.services.get('alarm'); @@ -84,28 +82,25 @@ class ServerHealthService extends BaseService { if ( process.platform !== 'linux' ) { return; } - - if ( this.config.no_system_checks ) return; + if ( this.config.no_system_checks ) return; /** * Adds a health check to the service. - * + * * @param {string} name - The name of the health check. * @param {Function} fn - The function to execute for the health check. * @returns {Object} A chainable object to add failure handlers. */ this.add_check('ram-usage', async () => { - const meminfo_text = await this.modules.fs.promises.readFile( - '/proc/meminfo', 'utf8' - ); + const meminfo_text = await this.modules.fs.promises.readFile('/proc/meminfo', 'utf8'); const meminfo = this.linuxutil.parse_meminfo(meminfo_text); const log_fields = { mem_free: meminfo.MemFree, mem_available: meminfo.MemAvailable, mem_total: meminfo.MemTotal, }; - + this.log.debug('memory', log_fields); Object.assign(this.stats_, log_fields); @@ -116,12 +111,11 @@ class ServerHealthService extends BaseService { }); } - /** * Initializes service health checks by setting up periodic checks. * This method configures an interval-based execution of health checks, * handles timeouts, and manages failure states. - * + * * @param {none} - This method does not take any parameters. * @returns {void} - This method does not return any value. */ @@ -129,11 +123,11 @@ class ServerHealthService extends BaseService { const svc_alarm = this.services.get('alarm'); /** * Initializes periodic health checks for the server. - * + * * This method sets up an interval to run all registered health checks * at a specified frequency. It manages the execution of checks, handles * timeouts, and logs errors or triggers alarms when checks fail. - * + * * @private * @method init_service_checks_ * @memberof ServerHealthService @@ -147,7 +141,7 @@ class ServerHealthService extends BaseService { const p_timeout = new promise.TeePromise(); /** * Creates a TeePromise to handle potential timeouts during health checks. - * + * * @returns {Promise} A promise that can be resolved or rejected from multiple places. */ const timeout = setTimeout(() => { @@ -166,14 +160,12 @@ class ServerHealthService extends BaseService { return; } - svc_alarm.create( - 'health-check-failure', - `Health check ${name} failed`, - { error: err } - ); + svc_alarm.create('health-check-failure', + `Health check ${name} failed`, + { error: err }); check_failures.push({ name }); - - this.log.error(`Error for healthcheck fail on ${name}: ` + err.stack); + + this.log.error(`Error for healthcheck fail on ${name}: ${ err.stack}`); // Run the on_fail handlers for ( const fn of chainable.on_fail_ ) { @@ -189,19 +181,16 @@ class ServerHealthService extends BaseService { this.failures_ = check_failures; }, 10 * time.SECOND, null, { onBehindSchedule: (drift) => { - svc_alarm.create( - 'health-checks-behind-schedule', - 'Health checks are behind schedule', - { drift } - ); - } + svc_alarm.create('health-checks-behind-schedule', + 'Health checks are behind schedule', + { drift }); + }, }); } - /** * Retrieves the current server health statistics. - * + * * @returns {Object} An object containing the current health statistics. * This method returns a shallow copy of the internal `stats_` object to prevent * direct manipulation of the service's data. @@ -222,10 +211,9 @@ class ServerHealthService extends BaseService { return chainable; } - /** * Retrieves the current health status of the server. - * + * * @returns {Object} An object containing: * - `ok` {boolean}: Indicates if all health checks passed. * - `failed` {Array}: An array of names of failed health checks, if any. diff --git a/src/backend/src/modules/core/lib/__lib__.js b/src/backend/src/modules/core/lib/__lib__.js index 43e858500f..05448a802c 100644 --- a/src/backend/src/modules/core/lib/__lib__.js +++ b/src/backend/src/modules/core/lib/__lib__.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ diff --git a/src/backend/src/modules/core/lib/expect.js b/src/backend/src/modules/core/lib/expect.js index 8bf9cd6e8e..4992a20d59 100644 --- a/src/backend/src/modules/core/lib/expect.js +++ b/src/backend/src/modules/core/lib/expect.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -32,7 +32,7 @@ class WorkUnit { * * @class */ - + /** * Creates and returns a new instance of WorkUnit. * @@ -52,7 +52,7 @@ class WorkUnit { this.checkpoint_ = null; } checkpoint (label) { - if ( ( global_config.logging ?? [] ).includes('checkpoint') ) { + if ( (global_config.logging ?? [] ).includes('checkpoint') ) { console.log('CHECKPOINT', label); } this.checkpoint_ = label; @@ -82,11 +82,9 @@ class CheckpointExpectation { } report (log) { if ( this.check() ) return; - log.log( - `operation(${this.workUnit.id}): ` + + log.log(`operation(${this.workUnit.id}): ` + `expected ${JSON.stringify(this.checkpoint)} ` + - `and got ${JSON.stringify(this.workUnit.checkpoint_)}.` - ); + `and got ${JSON.stringify(this.workUnit.checkpoint_)}.`); } } diff --git a/src/backend/src/modules/core/lib/identifier.js b/src/backend/src/modules/core/lib/identifier.js index bd551e5e5f..2570e77ac6 100644 --- a/src/backend/src/modules/core/lib/identifier.js +++ b/src/backend/src/modules/core/lib/identifier.js @@ -18,18 +18,18 @@ * along with this program. If not, see . */ const adjectives = [ - 'amazing', 'ambitious', 'articulate', 'cool', 'bubbly', 'mindful', 'noble', 'savvy', 'serene', + 'amazing', 'ambitious', 'articulate', 'cool', 'bubbly', 'mindful', 'noble', 'savvy', 'serene', 'sincere', 'sleek', 'sparkling', 'spectacular', 'splendid', 'spotless', 'stunning', 'awesome', 'beaming', 'bold', 'brilliant', 'cheerful', 'modest', 'motivated', 'friendly', 'fun', 'funny', 'generous', 'gifted', 'graceful', 'grateful', 'passionate', 'patient', 'peaceful', 'perceptive', 'persistent', - 'helpful', 'sensible', 'loyal', 'honest', 'clever', 'capable', + 'helpful', 'sensible', 'loyal', 'honest', 'clever', 'capable', 'calm', 'smart', 'genius', 'bright', 'charming', 'creative', 'diligent', 'elegant', 'fancy', - 'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent', + 'colorful', 'avid', 'active', 'gentle', 'happy', 'intelligent', 'jolly', 'kind', 'lively', 'merry', 'nice', 'optimistic', 'polite', - 'quiet', 'relaxed', 'silly', 'witty', 'young', - 'strong', 'brave', 'agile', 'bold', 'confident', 'daring', - 'fearless', 'heroic', 'mighty', 'powerful', 'valiant', 'wise', 'wonderful', 'zealous', + 'quiet', 'relaxed', 'silly', 'witty', 'young', + 'strong', 'brave', 'agile', 'bold', 'confident', 'daring', + 'fearless', 'heroic', 'mighty', 'powerful', 'valiant', 'wise', 'wonderful', 'zealous', 'warm', 'swift', 'neat', 'tidy', 'nifty', 'lucky', 'keen', 'blue', 'red', 'aqua', 'green', 'orange', 'pink', 'purple', 'cyan', 'magenta', 'lime', 'teal', 'lavender', 'beige', 'maroon', 'navy', 'olive', 'silver', 'gold', 'ivory', @@ -40,13 +40,13 @@ const nouns = [ 'magnet', 'chair', 'table', 'house', 'room', 'book', 'car', 'tree', 'candle', 'light', 'planet', 'flower', 'bird', 'fish', 'sun', 'moon', 'star', 'cloud', 'rain', 'snow', 'wind', 'mountain', 'river', 'lake', 'sea', 'ocean', 'island', 'bridge', 'road', 'train', 'plane', 'ship', 'bicycle', - 'circle', 'square', 'garden', 'harp', 'grass', 'forest', 'rock', 'cake', 'pie', 'cookie', 'candy', - 'butterfly', 'computer', 'phone', 'keyboard', 'mouse', 'cup', 'plate', 'glass', 'door', - 'window', 'key', 'wallet', 'pillow', 'bed', 'blanket', 'soap', 'towel', 'lamp', 'mirror', - 'camera', 'hat', 'shirt', 'pants', 'shoes', 'watch', 'ring', + 'circle', 'square', 'garden', 'harp', 'grass', 'forest', 'rock', 'cake', 'pie', 'cookie', 'candy', + 'butterfly', 'computer', 'phone', 'keyboard', 'mouse', 'cup', 'plate', 'glass', 'door', + 'window', 'key', 'wallet', 'pillow', 'bed', 'blanket', 'soap', 'towel', 'lamp', 'mirror', + 'camera', 'hat', 'shirt', 'pants', 'shoes', 'watch', 'ring', 'necklace', 'ball', 'toy', 'doll', 'kite', 'balloon', 'guitar', 'violin', 'piano', 'drum', 'trumpet', 'flute', 'viola', 'cello', 'harp', 'banjo', 'tuba', -] +]; const words = { adjectives, @@ -55,7 +55,7 @@ const words = { /** * Select a random item from an array using a random number generator function. - * + * * @param {Array} arr - The array to select an item from * @param {function} [random=Math.random] - Random number generator function * @returns {T} A random item from the array @@ -73,11 +73,11 @@ const randomItem = (arr, random) => arr[Math.floor((random ?? Math.random)() * a * * @example * - * let identifier = window.generate_identifier(); + * let identifier = window.generate_identifier(); * // identifier would be something like 'clever-idea-123' * */ -function generate_identifier(separator = '_', rng = Math.random){ +function generate_identifier (separator = '_', rng = Math.random) { // return a random combination of first_adj + noun + number (between 0 and 9999) // e.g. clever-idea-123 return [ @@ -90,9 +90,9 @@ function generate_identifier(separator = '_', rng = Math.random){ // Character set used for generating human-readable, case-insensitive random codes const HUMAN_READABLE_CASE_INSENSITIVE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; -function generate_random_code(n, { +function generate_random_code (n, { rng = Math.random, - chars = HUMAN_READABLE_CASE_INSENSITIVE + chars = HUMAN_READABLE_CASE_INSENSITIVE, } = {}) { let code = ''; for ( let i = 0 ; i < n ; i++ ) { @@ -107,7 +107,7 @@ function generate_random_code(n, { * @param {number} value - Number to convert to base-36 and append to the right * @returns {string} Combined uppercase code */ -function compose_code(mask, value) { +function compose_code (mask, value) { const right_str = value.toString(36); let out_str = mask; console.log('right_str', right_str); @@ -125,4 +125,3 @@ module.exports = { generate_identifier, generate_random_code, }; - diff --git a/src/backend/src/modules/core/lib/linux.js b/src/backend/src/modules/core/lib/linux.js index e9e8594805..b8ef87b46d 100644 --- a/src/backend/src/modules/core/lib/linux.js +++ b/src/backend/src/modules/core/lib/linux.js @@ -33,9 +33,8 @@ const parse_meminfo = text => { } return meminfo; -} +}; module.exports = { parse_meminfo, }; - diff --git a/src/backend/src/modules/core/lib/log.js b/src/backend/src/modules/core/lib/log.js index 0d6b941c36..caa98b8c29 100644 --- a/src/backend/src/modules/core/lib/log.js +++ b/src/backend/src/modules/core/lib/log.js @@ -26,7 +26,6 @@ const config = require('../../../config.js'); // (next month) log("tick"); // → "11-01 00:00:01 tick" // (next year) log("tick"); // → "2026-01-01 00:00:01 tick" - /** * Stringifies a log entry into a formatted string for console output. * @param {Object} logEntry - The log entry object containing: @@ -48,7 +47,7 @@ const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects lines.push(m); m = ''; }; - + m = ''; if ( ! config.show_relative_time ) { @@ -57,7 +56,7 @@ const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects m += prefix ? `${prefix} ` : ''; let levelLabelShown = false; - if ( log_lvl.label !== 'INFO' || ! config.log_hide_info_label ) { + if ( log_lvl.label !== 'INFO' || !config.log_hide_info_label ) { levelLabelShown = true; m += `\x1B[${log_lvl.esc}m[${log_lvl.label}\x1B[0m`; } else { @@ -98,7 +97,7 @@ const stringify_log_entry = ({ prefix, log_lvl, crumbs, message, fields, objects let v; try { v = colorize(JSON.stringify(fields[k])); } catch (e) { - v = '' + fields[k]; + v = `${ fields[k]}`; } m += ` \x1B[1m${k}:\x1B[0m ${v}`; lf(); diff --git a/src/backend/src/modules/core/lib/stdio.js b/src/backend/src/modules/core/lib/stdio.js index ca7b912838..07f4552b6d 100644 --- a/src/backend/src/modules/core/lib/stdio.js +++ b/src/backend/src/modules/core/lib/stdio.js @@ -41,30 +41,28 @@ const split_lines = (str) => { const lines = []; let line = ''; let line_length = 0; - for (const c of str) { + for ( const c of str ) { line += c; - if (c === '\n') { + if ( c === '\n' ) { lines.push(line); line = ''; line_length = 0; } else { line_length++; - if (line_length >= process.stdout.columns) { + if ( line_length >= process.stdout.columns ) { lines.push(line); line = ''; line_length = 0; } } } - if (line.length) { + if ( line.length ) { lines.push(line); } return lines; }; - module.exports = { visible_length, split_lines, }; - diff --git a/src/backend/src/modules/development/DevelopmentModule.js b/src/backend/src/modules/development/DevelopmentModule.js index 409f9dea39..bff84ded0f 100644 --- a/src/backend/src/modules/development/DevelopmentModule.js +++ b/src/backend/src/modules/development/DevelopmentModule.js @@ -1,27 +1,27 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); /** * Enable this module when you want performance monitoring. - * + * * Performance monitoring requires additional setup. Jaegar should be installed * and running. */ @@ -29,7 +29,7 @@ class DevelopmentModule extends AdvancedBase { async install (context) { const services = context.get('services'); - const LocalTerminalService = require("./LocalTerminalService"); + const LocalTerminalService = require('./LocalTerminalService'); services.registerService('local-terminal', LocalTerminalService); } } diff --git a/src/backend/src/modules/development/LocalTerminalService.js b/src/backend/src/modules/development/LocalTerminalService.js index 205ced9bcb..111d4696a0 100644 --- a/src/backend/src/modules/development/LocalTerminalService.js +++ b/src/backend/src/modules/development/LocalTerminalService.js @@ -17,18 +17,17 @@ * along with this program. If not, see . */ -const { spawn } = require("child_process"); -const APIError = require("../../api/APIError"); -const configurable_auth = require("../../middleware/configurable_auth"); -const { Endpoint } = require("../../util/expressutil"); - +const { spawn } = require('child_process'); +const APIError = require('../../api/APIError'); +const configurable_auth = require('../../middleware/configurable_auth'); +const { Endpoint } = require('../../util/expressutil'); const PERM_LOCAL_TERMINAL = 'local-terminal:access'; const path_ = require('path'); -const { Actor } = require("../../services/auth/Actor"); -const BaseService = require("../../services/BaseService"); -const { Context } = require("../../util/context"); +const { Actor } = require('../../services/auth/Actor'); +const BaseService = require('../../services/BaseService'); +const { Context } = require('../../util/context'); class LocalTerminalService extends BaseService { _construct () { @@ -37,15 +36,13 @@ class LocalTerminalService extends BaseService { get_profiles () { return { ['api-test']: { - cwd: path_.join( - __dirname, - '../../../../../', - 'tools/api-tester', - ), + cwd: path_.join(__dirname, + '../../../../../', + 'tools/api-tester'), shell: [ '/usr/bin/env', 'node', 'apitest.js', - '--config=config.yml' + '--config=config.yml', ], allow_args: true, }, @@ -55,7 +52,7 @@ class LocalTerminalService extends BaseService { const r_group = (() => { const require = this.require; const express = require('express'); - return express.Router() + return express.Router(); })(); app.use('/local-terminal', r_group); @@ -70,7 +67,7 @@ class LocalTerminalService extends BaseService { const actor = Context.get('actor'); const can_access = actor && await svc_permission.check(actor, PERM_LOCAL_TERMINAL); - + if ( ! can_access ) { throw APIError.create('permission_denied', null, { permission: PERM_LOCAL_TERMINAL, @@ -107,41 +104,35 @@ class LocalTerminalService extends BaseService { proc.stdout.on('data', data => { const base64 = data.toString('base64'); console.log('---------------------- CHUNK?', base64); - svc_socketio.send( - { room: req.user.id }, - 'local-terminal.stdout', - { - term_uuid, - base64, - }, - ); + svc_socketio.send({ room: req.user.id }, + 'local-terminal.stdout', + { + term_uuid, + base64, + }); }); proc.stderr.on('data', data => { const base64 = data.toString('base64'); console.log('---------------------- CHUNK?', base64); - svc_socketio.send( - { room: req.user.id }, - 'local-terminal.stderr', - { - term_uuid, - base64, - }, - ); + svc_socketio.send({ room: req.user.id }, + 'local-terminal.stderr', + { + term_uuid, + base64, + }); }); } - + proc.on('exit', () => { this.log.noticeme(`[${term_uuid}] Process exited (${proc.exitCode})`); delete this.sessions_[term_uuid]; const svc_socketio = req.services.get('socketio'); - svc_socketio.send( - { room: req.user.id }, - 'local-terminal.exit', - { - term_uuid, - }, - ); + svc_socketio.send({ room: req.user.id }, + 'local-terminal.exit', + { + term_uuid, + }); }); this.sessions_[term_uuid] = { @@ -178,7 +169,7 @@ class LocalTerminalService extends BaseService { const base64 = Buffer.from(msg.data, 'base64'); session.proc.stdin.write(base64); - }) + }); }); } } diff --git a/src/backend/src/modules/dns/DNSModule.js b/src/backend/src/modules/dns/DNSModule.js index 45abb606aa..7049a3e254 100644 --- a/src/backend/src/modules/dns/DNSModule.js +++ b/src/backend/src/modules/dns/DNSModule.js @@ -1,9 +1,9 @@ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class DNSModule extends AdvancedBase { async install (context) { const services = context.get('services'); - + const { DNSService } = require('./DNSService'); services.registerService('dns', DNSService); } diff --git a/src/backend/src/modules/dns/DNSService.js b/src/backend/src/modules/dns/DNSService.js index 6c75fc2ff1..8505c41bfa 100644 --- a/src/backend/src/modules/dns/DNSService.js +++ b/src/backend/src/modules/dns/DNSService.js @@ -1,5 +1,5 @@ -const BaseService = require("../../services/BaseService"); -const { sleep } = require("../../util/asyncutil"); +const BaseService = require('../../services/BaseService'); +const { sleep } = require('../../util/asyncutil'); /** * DNS service that provides DNS client functionality and optional test server @@ -17,12 +17,12 @@ class DNSService extends BaseService { nameServers: ['127.0.0.1'], port: 5300, }); - + if ( this.config.test_server ) { this.test_server_(); } } - + /** * Returns the DNS client instance * @returns {Object} The DNS client @@ -30,22 +30,22 @@ class DNSService extends BaseService { get_client () { return this.dns; } - + /** * Creates and starts a test DNS server that responds to A and TXT record queries * The server listens on port 5300 and returns mock responses for testing purposes */ test_server_ () { const dns2 = require('dns2'); - const { Packet } = dns2 - + const { Packet } = dns2; + const server = dns2.createServer({ udp: true, handle: (request, send, rinfo) => { const { questions } = request; const response = Packet.createResponseFromRequest(request); - for (const question of questions) { - if (question.type === Packet.TYPE.A || question.type === Packet.TYPE.ANY) { + for ( const question of questions ) { + if ( question.type === Packet.TYPE.A || question.type === Packet.TYPE.ANY ) { response.answers.push({ name: question.name, type: Packet.TYPE.A, @@ -55,45 +55,47 @@ class DNSService extends BaseService { }); } - if (question.type === Packet.TYPE.TXT || question.type === Packet.TYPE.ANY) { + if ( question.type === Packet.TYPE.TXT || question.type === Packet.TYPE.ANY ) { response.answers.push({ name: question.name, type: Packet.TYPE.TXT, class: Packet.CLASS.IN, ttl: 300, data: [ - JSON.stringify({ username: 'ed3' }) + JSON.stringify({ username: 'ed3' }), ], }); } } send(response); - } + }, }); server.on('listening', () => { this.log.debug('Fake DNS server listening', server.addresses()); - - if ( this.config.test_server_selftest ) (async () => { - await sleep(5000); - { - console.log('Trying first test') - const result = await this.dns.resolveA('test.local'); - console.log('Test 1', result); - } - { - console.log('Trying second test') - const result = await this.dns.resolve(`_puter-verify.test.local`, 'TXT'); - console.log('Test 2', result); - } - })(); + + if ( this.config.test_server_selftest ) { + (async () => { + await sleep(5000); + { + console.log('Trying first test'); + const result = await this.dns.resolveA('test.local'); + console.log('Test 1', result); + } + { + console.log('Trying second test'); + const result = await this.dns.resolve('_puter-verify.test.local', 'TXT'); + console.log('Test 2', result); + } + })(); + } }); - + server.on('close', () => { console.log('Fake DNS server closed'); this.log.noticeme('Fake DNS server closed'); - }) - + }); + server.on('request', (request, response, rinfo) => { console.log(request.header.id, request.questions[0]); }); @@ -102,11 +104,10 @@ class DNSService extends BaseService { console.log('Client sent an invalid request', error); }); - server.listen({ udp: { port: 5300, - address: "127.0.0.1", + address: '127.0.0.1', }, }); } diff --git a/src/backend/src/modules/domain/DomainModule.js b/src/backend/src/modules/domain/DomainModule.js index 8713b80b8b..c592f328b2 100644 --- a/src/backend/src/modules/domain/DomainModule.js +++ b/src/backend/src/modules/domain/DomainModule.js @@ -1,4 +1,4 @@ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class DomainModule extends AdvancedBase { async install (context) { diff --git a/src/backend/src/modules/domain/DomainVerificationService.js b/src/backend/src/modules/domain/DomainVerificationService.js index 5668480c42..f8c76ad96f 100644 --- a/src/backend/src/modules/domain/DomainVerificationService.js +++ b/src/backend/src/modules/domain/DomainVerificationService.js @@ -1,5 +1,5 @@ -const { get_user } = require("../../helpers"); -const BaseService = require("../../services/BaseService"); +const { get_user } = require('../../helpers'); +const BaseService = require('../../services/BaseService'); class DomainVerificationService extends BaseService { _init () { @@ -7,7 +7,7 @@ class DomainVerificationService extends BaseService { } async get_controlling_user ({ domain }) { const svc_event = this.services.get('event'); - + // 1 :: Allow event listeners to verify domains const event = { domain, @@ -17,7 +17,7 @@ class DomainVerificationService extends BaseService { if ( event.user ) { return event.user; } - + // 2 :: If there is no controlling user, 'admin' is the // controlling user. return await get_user({ username: 'admin' }); @@ -32,8 +32,8 @@ class DomainVerificationService extends BaseService { handler: async (args, log) => { const res = await this.get_controlling_user({ domain: args[0] }); log.log(res); - } - } + }, + }, ]); } } diff --git a/src/backend/src/modules/domain/TXTVerifyService.js b/src/backend/src/modules/domain/TXTVerifyService.js index 7b535e0436..33cebe08be 100644 --- a/src/backend/src/modules/domain/TXTVerifyService.js +++ b/src/backend/src/modules/domain/TXTVerifyService.js @@ -1,6 +1,6 @@ -const { get_user } = require("../../helpers"); -const BaseService = require("../../services/BaseService"); -const { atimeout } = require("../../util/asyncutil"); +const { get_user } = require('../../helpers'); +const BaseService = require('../../services/BaseService'); +const { atimeout } = require('../../util/asyncutil'); class TXTVerifyService extends BaseService { ['__on_boot.consolidation'] () { @@ -11,26 +11,22 @@ class TXTVerifyService extends BaseService { svc_event.on('domain.get-controlling-user', async (_, event) => { const record_name = `_puter-verify.${event.domain}`; try { - const result = await atimeout( - 5000, - dns.resolve(record_name, 'TXT'), - ); - - const answer = result.answers.filter( - a => a.name === record_name && - a.type === 16 - )[0]; - + const result = await atimeout(5000, + dns.resolve(record_name, 'TXT')); + + const answer = result.answers.filter(a => a.name === record_name && + a.type === 16)[0]; + const data_raw = answer.data; const data = JSON.parse(data_raw); event.user = await get_user({ username: data.username }); } catch (e) { console.error('ERROR', e); } - }) + }); } } module.exports = { TXTVerifyService, -} +}; diff --git a/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js b/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js index 53bd6a9ecf..5b6e7ff294 100644 --- a/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js +++ b/src/backend/src/modules/entitystore/EntityStoreInterfaceService.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2025-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // METADATA // {"ai-commented":{"service":"claude"}} -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); /** * Service class that manages Entity Store interface registrations. @@ -34,7 +34,7 @@ class EntityStoreInterfaceService extends BaseService { async ['__on_driver.register.interfaces'] () { const svc_registry = this.services.get('registry'); const col_interfaces = svc_registry.get('interfaces'); - + // Define the standard CRUD interface methods that will be reused const crudMethods = { create: { @@ -45,14 +45,14 @@ class EntityStoreInterfaceService extends BaseService { required: true, }, options: { type: 'json' }, - } + }, }, read: { parameters: { uid: { type: 'string' }, id: { type: 'json' }, params: { type: 'json' }, - } + }, }, select: { parameters: { @@ -60,7 +60,7 @@ class EntityStoreInterfaceService extends BaseService { offset: { type: 'number' }, limit: { type: 'number' }, params: { type: 'json' }, - } + }, }, update: { parameters: { @@ -71,7 +71,7 @@ class EntityStoreInterfaceService extends BaseService { required: true, }, options: { type: 'json' }, - } + }, }, upsert: { parameters: { @@ -82,47 +82,47 @@ class EntityStoreInterfaceService extends BaseService { required: true, }, options: { type: 'json' }, - } + }, }, delete: { parameters: { uid: { type: 'string' }, id: { type: 'json' }, - } + }, }, }; - + // Register the crud-q interface col_interfaces.set('crud-q', { - methods: { ...crudMethods } + methods: { ...crudMethods }, }); // Register entity-specific interfaces that use crud-q const entityInterfaces = [ { name: 'puter-apps', - description: 'Manage a developer\'s apps on Puter.' + description: 'Manage a developer\'s apps on Puter.', }, { name: 'puter-subdomains', - description: 'Manage subdomains on Puter.' + description: 'Manage subdomains on Puter.', }, { name: 'puter-notifications', - description: 'Read notifications on Puter.' - } + description: 'Read notifications on Puter.', + }, ]; // Register each entity interface with the same CRUD methods - for (const entity of entityInterfaces) { + for ( const entity of entityInterfaces ) { col_interfaces.set(entity.name, { description: entity.description, - methods: { ...crudMethods } + methods: { ...crudMethods }, }); } } } module.exports = { - EntityStoreInterfaceService + EntityStoreInterfaceService, }; \ No newline at end of file diff --git a/src/backend/src/modules/entitystore/EntityStoreModule.js b/src/backend/src/modules/entitystore/EntityStoreModule.js index fa42c8789e..afad23600b 100644 --- a/src/backend/src/modules/entitystore/EntityStoreModule.js +++ b/src/backend/src/modules/entitystore/EntityStoreModule.js @@ -1,32 +1,32 @@ /* * Copyright (C) 2025-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { EntityStoreInterfaceService } = require("./EntityStoreInterfaceService"); +const { AdvancedBase } = require('@heyputer/putility'); +const { EntityStoreInterfaceService } = require('./EntityStoreInterfaceService'); /** * A module for registering entity store interfaces. */ class EntityStoreModule extends AdvancedBase { - async install(context) { + async install (context) { const services = context.get('services'); - + // Register interface services services.registerService('entitystore-interface', EntityStoreInterfaceService); } diff --git a/src/backend/src/modules/hostos/HostOSModule.js b/src/backend/src/modules/hostos/HostOSModule.js index c7e50439c2..0ee2e11369 100644 --- a/src/backend/src/modules/hostos/HostOSModule.js +++ b/src/backend/src/modules/hostos/HostOSModule.js @@ -1,4 +1,4 @@ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class HostOSModule extends AdvancedBase { async install (context) { diff --git a/src/backend/src/modules/hostos/ProcessService.js b/src/backend/src/modules/hostos/ProcessService.js index d96c33ac2b..1a4deb70ef 100644 --- a/src/backend/src/modules/hostos/ProcessService.js +++ b/src/backend/src/modules/hostos/ProcessService.js @@ -1,4 +1,4 @@ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class ProxyLogger { constructor (log) { @@ -9,7 +9,7 @@ class ProxyLogger { stream.on('data', (chunk) => { buffer += chunk.toString(); let lineEndIndex = buffer.indexOf('\n'); - while (lineEndIndex !== -1) { + while ( lineEndIndex !== -1 ) { const line = buffer.substring(0, lineEndIndex); this.log(line); buffer = buffer.substring(lineEndIndex + 1); @@ -18,7 +18,7 @@ class ProxyLogger { }); stream.on('end', () => { - if (buffer.length) { + if ( buffer.length ) { this.log(buffer); } }); @@ -42,15 +42,15 @@ class ProcessService extends BaseService { process.on('exit', () => { this.exit_all_(); - }) + }); } log_ (name, isErr, line) { let txt = `[${name}:`; txt += isErr - ? `\x1B[34;1m2\x1B[0m` - : `\x1B[32;1m1\x1B[0m`; - txt += '] ' + line; + ? '\x1B[34;1m2\x1B[0m' + : '\x1B[32;1m1\x1B[0m'; + txt += `] ${ line}`; this.log.info(txt); } @@ -66,13 +66,11 @@ class ProcessService extends BaseService { for ( const k in env_processed ) { if ( typeof env_processed[k] !== 'function' ) continue; env_processed[k] = env_processed[k]({ - global_config: this.global_config + global_config: this.global_config, }); } - this.log.debug( - 'command', - { command, args }, - ); + this.log.debug('command', + { command, args }); const proc = this.modules.spawn(command, args, { shell: true, env: { @@ -91,7 +89,7 @@ class ProcessService extends BaseService { proc.on('exit', () => { this.log.info(`[${name}:exit] Process exited (${proc.exitCode})`); this.instances = this.instances.filter((inst) => inst.proc !== proc); - }) + }); } } diff --git a/src/backend/src/modules/internet/InternetModule.js b/src/backend/src/modules/internet/InternetModule.js index fc07a145bc..84a13af55d 100644 --- a/src/backend/src/modules/internet/InternetModule.js +++ b/src/backend/src/modules/internet/InternetModule.js @@ -1,11 +1,11 @@ -const { AdvancedBase } = require("@heyputer/putility"); -const config = require("../../config.js"); +const { AdvancedBase } = require('@heyputer/putility'); +const config = require('../../config.js'); class InternetModule extends AdvancedBase { async install (context) { const services = context.get('services'); - if ( !! config?.services?.['wisp-relay'] ) { + if ( config?.services?.['wisp-relay'] ) { const WispRelayService = require('./WispRelayService.js'); services.registerService('wisp-relay', WispRelayService); } diff --git a/src/backend/src/modules/internet/WispRelayService.js b/src/backend/src/modules/internet/WispRelayService.js index c781668d48..95f012f7a4 100644 --- a/src/backend/src/modules/internet/WispRelayService.js +++ b/src/backend/src/modules/internet/WispRelayService.js @@ -1,4 +1,4 @@ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class WispRelayService extends BaseService { _init () { diff --git a/src/backend/src/modules/kvstore/KVStoreInterfaceService.js b/src/backend/src/modules/kvstore/KVStoreInterfaceService.js index a902052e51..12cfae9a96 100644 --- a/src/backend/src/modules/kvstore/KVStoreInterfaceService.js +++ b/src/backend/src/modules/kvstore/KVStoreInterfaceService.js @@ -64,7 +64,7 @@ class KVStoreInterfaceService extends BaseService { * Service class for managing KVStore interface registrations. * Extends the base service to provide key-value store interface management. */ - async ['__on_driver.register.interfaces']() { + async ['__on_driver.register.interfaces'] () { const svc_registry = this.services.get('registry'); const col_interfaces = svc_registry.get('interfaces'); diff --git a/src/backend/src/modules/kvstore/KVStoreModule.js b/src/backend/src/modules/kvstore/KVStoreModule.js index 55dd597092..101e7c000a 100644 --- a/src/backend/src/modules/kvstore/KVStoreModule.js +++ b/src/backend/src/modules/kvstore/KVStoreModule.js @@ -1,32 +1,32 @@ /* * Copyright (C) 2025-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { KVStoreInterfaceService } = require("./KVStoreInterfaceService"); +const { AdvancedBase } = require('@heyputer/putility'); +const { KVStoreInterfaceService } = require('./KVStoreInterfaceService'); /** * A module for registering key-value store interfaces. */ class KVStoreModule extends AdvancedBase { - async install(context) { + async install (context) { const services = context.get('services'); - + // Register interface services services.registerService('kvstore-interface', KVStoreInterfaceService); } diff --git a/src/backend/src/modules/perfmon/PerfMonModule.js b/src/backend/src/modules/perfmon/PerfMonModule.js index f27e38ff62..c76d7a365e 100644 --- a/src/backend/src/modules/perfmon/PerfMonModule.js +++ b/src/backend/src/modules/perfmon/PerfMonModule.js @@ -1,27 +1,27 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); /** * Enable this module when you want performance monitoring. - * + * * Performance monitoring requires additional setup. Jaegar should be installed * and running. */ @@ -29,7 +29,7 @@ class PerfMonModule extends AdvancedBase { async install (context) { const services = context.get('services'); - const TelemetryService = require("./TelemetryService"); + const TelemetryService = require('./TelemetryService'); services.registerService('telemetry', TelemetryService); } } diff --git a/src/backend/src/modules/perfmon/TelemetryService.js b/src/backend/src/modules/perfmon/TelemetryService.js index 4444044bde..7d32b46b60 100644 --- a/src/backend/src/modules/perfmon/TelemetryService.js +++ b/src/backend/src/modules/perfmon/TelemetryService.js @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const opentelemetry = require("@opentelemetry/api"); +const opentelemetry = require('@opentelemetry/api'); const { NodeSDK } = require('@opentelemetry/sdk-node'); const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); const { PeriodicExportingMetricReader, ConsoleMetricExporter } = require('@opentelemetry/sdk-metrics'); -const { Resource } = require("@opentelemetry/resources"); -const { SemanticResourceAttributes } = require("@opentelemetry/semantic-conventions"); -const { NodeTracerProvider } = require("@opentelemetry/sdk-trace-node"); -const { ConsoleSpanExporter, BatchSpanProcessor } = require("@opentelemetry/sdk-trace-base"); +const { Resource } = require('@opentelemetry/resources'); +const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions'); +const { NodeTracerProvider } = require('@opentelemetry/sdk-trace-node'); +const { ConsoleSpanExporter, BatchSpanProcessor } = require('@opentelemetry/sdk-trace-base'); const config = require('../../config'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc'); @@ -33,13 +33,12 @@ const BaseService = require('../../services/BaseService'); class TelemetryService extends BaseService { _construct () { const resource = Resource.default().merge( - new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: "puter-backend", - [SemanticResourceAttributes.SERVICE_VERSION]: "0.1.0" - }), - ); + new Resource({ + [SemanticResourceAttributes.SERVICE_NAME]: 'puter-backend', + [SemanticResourceAttributes.SERVICE_VERSION]: '0.1.0', + })); - const provider = new NodeTracerProvider({ resource }) + const provider = new NodeTracerProvider({ resource }); const exporter = this.getConfiguredExporter_(); this.exporter = exporter; @@ -51,20 +50,18 @@ class TelemetryService extends BaseService { const sdk = new NodeSDK({ traceExporter: new ConsoleSpanExporter(), metricReader: new PeriodicExportingMetricReader({ - exporter: new ConsoleMetricExporter() + exporter: new ConsoleMetricExporter(), }), - instrumentations: [getNodeAutoInstrumentations()] + instrumentations: [getNodeAutoInstrumentations()], }); this.sdk = sdk; this.sdk.start(); - this.tracer_ = opentelemetry.trace.getTracer( - 'puter-tracer' - ); + this.tracer_ = opentelemetry.trace.getTracer('puter-tracer'); } - + _init () { const svc_context = this.services.get('context'); svc_context.register_context_hook('pre_arun', ({ hints, trace_name, callback, replace_callback }) => { @@ -75,7 +72,7 @@ class TelemetryService extends BaseService { return await this.tracer_.startActiveSpan(trace_name, async span => { try { return await callback(); - } catch (error) { + } catch ( error ) { span.setStatus({ code: opentelemetry.SpanStatusCode.ERROR, message: error.message }); throw error; } finally { @@ -86,7 +83,7 @@ class TelemetryService extends BaseService { }); } - getConfiguredExporter_() { + getConfiguredExporter_ () { if ( config.jaeger ?? this.config.jaeger ) { return new OTLPTraceExporter(config.jaeger ?? this.config.jaeger); } diff --git a/src/backend/src/modules/puterai/AIInterfaceService.js b/src/backend/src/modules/puterai/AIInterfaceService.js index a8804d9508..eed23a7ea4 100644 --- a/src/backend/src/modules/puterai/AIInterfaceService.js +++ b/src/backend/src/modules/puterai/AIInterfaceService.js @@ -1,25 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // METADATA // {"ai-commented":{"service":"claude"}} -const BaseService = require("../../services/BaseService"); - +const BaseService = require('../../services/BaseService'); /** * Service class that manages AI interface registrations and configurations. @@ -37,7 +36,7 @@ class AIInterfaceService extends BaseService { async ['__on_driver.register.interfaces'] () { const svc_registry = this.services.get('registry'); const col_interfaces = svc_registry.get('interfaces'); - + col_interfaces.set('puter-ocr', { description: 'Optical character recognition', methods: { @@ -81,10 +80,10 @@ class AIInterfaceService extends BaseService { type: { $: 'stream', content_type: 'image', - } + }, }, }, - } + }, }); col_interfaces.set('puter-chat-completion', { @@ -113,8 +112,8 @@ class AIInterfaceService extends BaseService { max_tokens: { type: 'number' }, }, result: { type: 'json' }, - } - } + }, + }, }); col_interfaces.set('puter-image-generation', { @@ -150,22 +149,22 @@ class AIInterfaceService extends BaseService { type: { $: 'stream', content_type: 'image', - } + }, }, { names: ['url'], type: { $: 'string:url:web', content_type: 'image', - } + }, }, ], result: { description: 'URL of the generated image.', - type: 'string' - } - } - } + type: 'string', + }, + }, + }, }); col_interfaces.set('puter-video-generation', { @@ -200,22 +199,22 @@ class AIInterfaceService extends BaseService { type: { $: 'string:url:web', content_type: 'video', - } + }, }, { names: ['video'], type: { $: 'stream', content_type: 'video', - } + }, }, ], result: { description: 'Video asset descriptor or URL for the generated video.', - type: 'json' - } - } - } + type: 'json', + }, + }, + }, }); col_interfaces.set('puter-tts', { @@ -254,12 +253,12 @@ class AIInterfaceService extends BaseService { type: { $: 'stream', content_type: 'audio', - } + }, }, - ] + ], }, - } - }) + }, + }); col_interfaces.set('puter-speech2txt', { description: 'Speech to text transcription and translation.', @@ -308,5 +307,5 @@ class AIInterfaceService extends BaseService { } module.exports = { - AIInterfaceService + AIInterfaceService, }; diff --git a/src/backend/src/modules/puterai/AITestModeService.js b/src/backend/src/modules/puterai/AITestModeService.js index 04ed6d1331..60aee8f5a1 100644 --- a/src/backend/src/modules/puterai/AITestModeService.js +++ b/src/backend/src/modules/puterai/AITestModeService.js @@ -1,25 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ // METADATA // {"ai-commented":{"service":"claude"}} -const BaseService = require("../../services/BaseService"); - +const BaseService = require('../../services/BaseService'); /** * Service class that handles AI test mode functionality. diff --git a/src/backend/src/modules/puterai/AWSPollyService.js b/src/backend/src/modules/puterai/AWSPollyService.js index bf3d41a1b7..bb86e990bc 100644 --- a/src/backend/src/modules/puterai/AWSPollyService.js +++ b/src/backend/src/modules/puterai/AWSPollyService.js @@ -26,10 +26,10 @@ const { Context } = require('../../util/context'); // Polly price calculation per engine const ENGINE_PRICING = { - 'standard': 400, // $4.00 per 1M characters - 'neural': 1600, // $16.00 per 1M characters - 'long-form': 10000, // $100.00 per 1M characters - 'generative': 3000, // $30.00 per 1M characters + 'standard': 400, // $4.00 per 1M characters + 'neural': 1600, // $16.00 per 1M characters + 'long-form': 10000, // $100.00 per 1M characters + 'generative': 3000, // $30.00 per 1M characters }; // Valid engine types @@ -46,7 +46,7 @@ const VALID_ENGINES = ['standard', 'neural', 'long-form', 'generative']; class AWSPollyService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService() { + get meteringService () { return this.services.get('meteringService').meteringService; } @@ -60,13 +60,13 @@ class AWSPollyService extends BaseService { * the internal state needed for AWS Polly client management. * @returns {Promise} */ - async _construct() { + async _construct () { this.clients_ = {}; } static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-tts' && method_name === 'synthesize'; }, }, @@ -79,7 +79,7 @@ class AWSPollyService extends BaseService { * @property {Object} synthesize - Converts text to speech using specified voice/language * @property {Function} supports_test_mode - Indicates test mode support for methods */ - async list_voices({ engine } = {}) { + async list_voices ({ engine } = {}) { const polly_voices = await this.describe_voices(); let voices = polly_voices.Voices; @@ -104,14 +104,14 @@ class AWSPollyService extends BaseService { return voices; }, - async list_engines() { + async list_engines () { return VALID_ENGINES.map(engine => ({ id: engine, name: engine.charAt(0).toUpperCase() + engine.slice(1), pricing_per_million_chars: ENGINE_PRICING[engine] / 100, // Convert microcents to dollars })); }, - async synthesize({ + async synthesize ({ text, voice, ssml, language, engine = 'standard', @@ -126,7 +126,7 @@ class AWSPollyService extends BaseService { } // Validate engine - if ( !VALID_ENGINES.includes(engine) ) { + if ( ! VALID_ENGINES.includes(engine) ) { throw APIError.create('invalid_engine', null, { engine, valid_engines: VALID_ENGINES }); } @@ -166,14 +166,14 @@ class AWSPollyService extends BaseService { * @private * @returns {Object} Object containing AWS access key ID and secret access key */ - _create_aws_credentials() { + _create_aws_credentials () { return { accessKeyId: this.config.aws.access_key, secretAccessKey: this.config.aws.secret_key, }; } - _get_client(region) { + _get_client (region) { if ( ! region ) { region = this.config.aws?.region ?? this.global_config.aws?.region ?? 'us-west-2'; @@ -194,7 +194,7 @@ class AWSPollyService extends BaseService { * @description Fetches voice information from AWS Polly API and caches it for 10 minutes * Uses KV store for caching to avoid repeated API calls */ - async describe_voices() { + async describe_voices () { let voices = this.modules.kv.get('svc:polly:voices'); if ( voices ) { this.log.debug('voices cache hit'); @@ -228,12 +228,12 @@ class AWSPollyService extends BaseService { * @param {string} [options.engine] - TTS engine to use ('standard', 'neural', 'long-form', 'generative') * @returns {Promise} The synthesized speech response */ - async synthesize_speech(text, { format, voice_id, language, text_type, engine = 'standard' }) { + async synthesize_speech (text, { format, voice_id, language, text_type, engine = 'standard' }) { const client = this._get_client(this.config.aws.region); let voice = voice_id ?? undefined; - if ( ! voice && language ) { + if ( !voice && language ) { this.log.debug('getting language appropriate voice', { language, engine }); voice = await this.maybe_get_language_appropriate_voice_(language, engine); } @@ -268,7 +268,7 @@ class AWSPollyService extends BaseService { * @returns {Promise} The voice ID if found, null if no matching voice exists * @private */ - async maybe_get_language_appropriate_voice_(language, engine = 'standard') { + async maybe_get_language_appropriate_voice_ (language, engine = 'standard') { const voices = await this.describe_voices(); const voice = voices.Voices.find((voice) => { @@ -288,7 +288,7 @@ class AWSPollyService extends BaseService { * @returns {Promise} The default voice ID for the engine * @private */ - async get_default_voice_for_engine_(engine = 'standard') { + async get_default_voice_for_engine_ (engine = 'standard') { const voices = await this.describe_voices(); // Common default voices for each engine diff --git a/src/backend/src/modules/puterai/AWSTextractService.js b/src/backend/src/modules/puterai/AWSTextractService.js index ddfc32a6b8..e67783513f 100644 --- a/src/backend/src/modules/puterai/AWSTextractService.js +++ b/src/backend/src/modules/puterai/AWSTextractService.js @@ -32,7 +32,7 @@ const { Context } = require('../../util/context'); */ class AWSTextractService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService(){ + get meteringService () { return this.services.get('meteringService').meteringService; } /** @@ -41,13 +41,13 @@ class AWSTextractService extends BaseService { * Implements interfaces for OCR recognition and driver capabilities * @extends BaseService */ - _construct() { + _construct () { this.clients_ = {}; } static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-ocr' && method_name === 'recognize'; }, }, @@ -59,7 +59,7 @@ class AWSTextractService extends BaseService { * @param {boolean} params.test_mode - If true, returns sample test output instead of processing * @returns {Promise} Recognition results containing blocks of text with confidence scores */ - async recognize({ source, test_mode }) { + async recognize ({ source, test_mode }) { if ( test_mode ) { return { blocks: [ @@ -110,14 +110,14 @@ class AWSTextractService extends BaseService { * @private * @returns {Object} Object containing AWS access key ID and secret access key */ - _create_aws_credentials() { + _create_aws_credentials () { return { accessKeyId: this.config.aws.access_key, secretAccessKey: this.config.aws.secret_key, }; } - _get_client(region) { + _get_client (region) { if ( ! region ) { region = this.config.aws?.region ?? this.global_config.aws?.region ?? 'us-west-2'; @@ -140,7 +140,7 @@ class AWSTextractService extends BaseService { * @description Processes document through Textract's AnalyzeDocument API with LAYOUT feature. * Will attempt to use S3 direct access first, falling back to buffer upload if needed. */ - async analyze_document(file_facade) { + async analyze_document (file_facade) { const { client, document, using_s3, } = await this._get_client_and_document(file_facade); @@ -207,9 +207,9 @@ class AWSTextractService extends BaseService { * @throws {APIError} If file does not exist * @throws {Error} If no suitable input format is available */ - async _get_client_and_document(file_facade, force_buffer) { + async _get_client_and_document (file_facade, force_buffer) { const try_s3info = await file_facade.get('s3-info'); - if ( try_s3info && ! force_buffer ) { + if ( try_s3info && !force_buffer ) { console.log('S3 INFO', try_s3info); return { using_s3: true, @@ -234,7 +234,7 @@ class AWSTextractService extends BaseService { } const fsNode = await file_facade.get('fs-node'); - if ( fsNode && ! await fsNode.exists() ) { + if ( fsNode && !await fsNode.exists() ) { throw APIError.create('subject_does_not_exist'); } diff --git a/src/backend/src/modules/puterai/DeepSeekService.js b/src/backend/src/modules/puterai/DeepSeekService.js index 4915b8e2d4..d61a50d8a4 100644 --- a/src/backend/src/modules/puterai/DeepSeekService.js +++ b/src/backend/src/modules/puterai/DeepSeekService.js @@ -43,7 +43,7 @@ class DeepSeekService extends BaseService { * Gets the system prompt used for AI interactions * @returns {string} The base system prompt that identifies the AI as running on Puter */ - adapt_model(model) { + adapt_model (model) { return model; } @@ -52,7 +52,7 @@ class DeepSeekService extends BaseService { * @private * @returns {Promise} Resolves when initialization is complete */ - async _init() { + async _init () { this.openai = new this.modules.openai.OpenAI({ apiKey: this.global_config.services.deepseek.apiKey, baseURL: 'https://api.deepseek.com', @@ -70,7 +70,7 @@ class DeepSeekService extends BaseService { * Returns the default model identifier for the DeepSeek service * @returns {string} The default model ID 'deepseek-chat' */ - get_default_model() { + get_default_model () { return 'deepseek-chat'; } @@ -82,7 +82,7 @@ class DeepSeekService extends BaseService { * * @returns Promise> Array of model details */ - async models() { + async models () { return await this.models_(); }, /** @@ -91,7 +91,7 @@ class DeepSeekService extends BaseService { * @description Retrieves all available model IDs and their aliases, * flattening them into a single array of strings that can be used for model selection */ - async list() { + async list () { const models = await this.models_(); const model_names = []; for ( const model of models ) { @@ -107,7 +107,7 @@ class DeepSeekService extends BaseService { * AI Chat completion method. * See AIChatService for more details. */ - async complete({ messages, stream, model, tools, max_tokens, temperature }) { + async complete ({ messages, stream, model, tools, max_tokens, temperature }) { model = this.adapt_model(model); messages = await OpenAIUtil.process_input_messages(messages); @@ -189,7 +189,7 @@ class DeepSeekService extends BaseService { * - cost: Pricing information object with currency and rates * @private */ - async models_() { + async models_ () { return [ { id: 'deepseek-chat', diff --git a/src/backend/src/modules/puterai/FakeChatService.js b/src/backend/src/modules/puterai/FakeChatService.js index d385f1e654..873be15e2e 100644 --- a/src/backend/src/modules/puterai/FakeChatService.js +++ b/src/backend/src/modules/puterai/FakeChatService.js @@ -18,8 +18,8 @@ */ // METADATA // {"ai-commented":{"service":"claude"}} -const { default: dedent } = require("dedent"); -const BaseService = require("../../services/BaseService"); +const { default: dedent } = require('dedent'); +const BaseService = require('../../services/BaseService'); /** * FakeChatService - A mock implementation of a chat service that extends BaseService. * Provides fake chat completion responses using Lorem Ipsum text generation. @@ -32,7 +32,7 @@ class FakeChatService extends BaseService { * @private * @returns {Promise} */ - async _init() { + async _init () { const svc_aiChat = this.services.get('ai-chat'); svc_aiChat.register_provider({ service_name: this.service_name, @@ -40,7 +40,7 @@ class FakeChatService extends BaseService { }); } - get_default_model() { + get_default_model () { return 'fake'; } static IMPLEMENTS = { @@ -51,7 +51,7 @@ class FakeChatService extends BaseService { * @description Returns detailed information about available models including * their costs for input and output tokens */ - async models() { + async models () { return [ { id: 'fake', @@ -65,8 +65,8 @@ class FakeChatService extends BaseService { id: 'costly', aliases: [], cost: { - input: 1000, // 1000 microcents per million tokens (0.001 cents per 1000 tokens) - output: 2000, // 2000 microcents per million tokens (0.002 cents per 1000 tokens) + input: 1000, // 1000 microcents per million tokens (0.001 cents per 1000 tokens) + output: 2000, // 2000 microcents per million tokens (0.002 cents per 1000 tokens) }, max_tokens: 8192, }, @@ -87,7 +87,7 @@ class FakeChatService extends BaseService { * @description Retrieves all available model IDs and their aliases, * flattening them into a single array of strings that can be used for model selection */ - async list() { + async list () { return ['fake', 'costly', 'abuse']; }, @@ -99,7 +99,7 @@ class FakeChatService extends BaseService { * @param {string} params.model - The model to use ('fake', 'costly', or 'abuse') * @returns {Object} A simulated chat completion response with Lorem Ipsum content */ - async complete({ messages, stream, model, max_tokens, custom }) { + async complete ({ messages, stream, model, max_tokens, custom }) { const { LoremIpsum } = require('lorem-ipsum'); const li = new LoremIpsum({ sentencesPerParagraph: { @@ -123,10 +123,10 @@ class FakeChatService extends BaseService { stream: true, init_chat_stream: async ({ chatStream }) => { await new Promise(rslv => setTimeout(rslv, 500)); - chatStream.stream.write(JSON.stringify({ + chatStream.stream.write(`${JSON.stringify({ type: 'text', text: resp.message.content[0].text, - }) + '\n'); + }) }\n`); chatStream.end(); }, }; @@ -137,7 +137,7 @@ class FakeChatService extends BaseService { }, }; - get_response({ li, usedModel, messages, custom, max_tokens }) { + get_response ({ li, usedModel, messages, custom, max_tokens }) { let inputTokens = 0; let outputTokens = 0; @@ -184,30 +184,30 @@ class FakeChatService extends BaseService { // Report usage based on model const usage = { - "input_tokens": usedModel === 'costly' ? inputTokens : 0, - "output_tokens": usedModel === 'costly' ? outputTokens : 1, + 'input_tokens': usedModel === 'costly' ? inputTokens : 0, + 'output_tokens': usedModel === 'costly' ? outputTokens : 1, }; return { - "index": 0, + 'index': 0, message: { - "id": "00000000-0000-0000-0000-000000000000", - "type": "message", - "role": "assistant", - "model": usedModel, - "content": [ + 'id': '00000000-0000-0000-0000-000000000000', + 'type': 'message', + 'role': 'assistant', + 'model': usedModel, + 'content': [ { - "type": "text", - "text": responseText, + 'type': 'text', + 'text': responseText, }, ], - "stop_reason": "end_turn", - "stop_sequence": null, - "usage": usage, + 'stop_reason': 'end_turn', + 'stop_sequence': null, + 'usage': usage, }, - "usage": usage, - "logprobs": null, - "finish_reason": "stop", + 'usage': usage, + 'logprobs': null, + 'finish_reason': 'stop', }; } } diff --git a/src/backend/src/modules/puterai/GeminiImageGenerationService.js b/src/backend/src/modules/puterai/GeminiImageGenerationService.js index a4d55d4bf8..c30ce8afd5 100644 --- a/src/backend/src/modules/puterai/GeminiImageGenerationService.js +++ b/src/backend/src/modules/puterai/GeminiImageGenerationService.js @@ -31,13 +31,13 @@ const { GoogleGenAI } = require('@google/genai'); */ class GeminiImageGenerationService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService(){ + get meteringService () { return this.services.get('meteringService').meteringService; } static MODULES = { }; - _construct() { + _construct () { this.models_ = { 'gemini-2.5-flash-image-preview': { '1024x1024': 0.039, @@ -51,13 +51,13 @@ class GeminiImageGenerationService extends BaseService { * @async * @returns {Promise} */ - async _init() { + async _init () { this.genAI = new GoogleGenAI({ apiKey: this.global_config.services.gemini.apiKey }); } static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-image-generation' && method_name === 'generate'; }, @@ -74,7 +74,7 @@ class GeminiImageGenerationService extends BaseService { * @returns {Promise} URL of the generated image * @throws {Error} If prompt is not a string or ratio is invalid */ - async generate(params) { + async generate (params) { const { prompt, quality, test_mode, model, ratio, input_image, input_image_mime_type } = params; if ( test_mode ) { @@ -106,7 +106,7 @@ class GeminiImageGenerationService extends BaseService { static RATIO_SQUARE = { w: 1024, h: 1024 }; - async generate(prompt, { + async generate (prompt, { ratio, model, input_image, @@ -117,7 +117,7 @@ class GeminiImageGenerationService extends BaseService { } if ( !ratio || !this._validate_ratio(ratio, model) ) { - throw new Error('`ratio` must be a valid ratio for model ' + model); + throw new Error(`\`ratio\` must be a valid ratio for model ${ model}`); } // Validate input image if provided @@ -136,21 +136,21 @@ class GeminiImageGenerationService extends BaseService { // Somewhat sane defaults model = model ?? 'gemini-2.5-flash-image-preview'; - if ( !this.models_[model] ) { + if ( ! this.models_[model] ) { throw APIError.create('field_invalid', null, { key: 'model', - expected: 'one of: ' + - Object.keys(this.models_).join(', '), + expected: `one of: ${ + Object.keys(this.models_).join(', ')}`, got: model, }); } const price_key = `${ratio.w}x${ratio.h}`; - if ( !this.models_[model][price_key] ) { + if ( ! this.models_[model][price_key] ) { const availableSizes = Object.keys(this.models_[model]); throw APIError.create('field_invalid', null, { key: 'size/quality combination', - expected: 'one of: ' + availableSizes.join(', '), + expected: `one of: ${ availableSizes.join(', ')}`, got: price_key, }); } @@ -169,7 +169,7 @@ class GeminiImageGenerationService extends BaseService { const usageAllowed = await this.meteringService.hasEnoughCreditsFor(actor, usageType, 1); - if ( !usageAllowed ) { + if ( ! usageAllowed ) { throw APIError.create('insufficient_funds'); } @@ -204,11 +204,11 @@ class GeminiImageGenerationService extends BaseService { // do nothing here } else if ( part.inlineData ) { const imageData = part.inlineData.data; - url = 'data:image/png;base64,' + imageData; + url = `data:image/png;base64,${ imageData}`; } } - if ( !url ) { + if ( ! url ) { throw new Error('Failed to extract image URL from Gemini response'); } @@ -221,13 +221,13 @@ class GeminiImageGenerationService extends BaseService { * @returns {Array} Array of valid ratio objects * @private */ - _getValidRatios(model) { + _getValidRatios (model) { if ( model === 'gemini-2.5-flash-image-preview' ) { return [this.constructor.RATIO_SQUARE]; } } - _validate_ratio(ratio, model) { + _validate_ratio (ratio, model) { const validRatios = this._getValidRatios(model); return validRatios.includes(ratio); } @@ -238,7 +238,7 @@ class GeminiImageGenerationService extends BaseService { * @returns {boolean} True if the MIME type is supported * @private */ - _validate_image_mime_type(mimeType) { + _validate_image_mime_type (mimeType) { const supportedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/webp']; return supportedTypes.includes(mimeType.toLowerCase()); } diff --git a/src/backend/src/modules/puterai/GeminiService.js b/src/backend/src/modules/puterai/GeminiService.js index 9d950887d1..92e16ee40f 100644 --- a/src/backend/src/modules/puterai/GeminiService.js +++ b/src/backend/src/modules/puterai/GeminiService.js @@ -10,7 +10,7 @@ class GeminiService extends BaseService { */ meteringService = undefined; - async _init() { + async _init () { const svc_aiChat = this.services.get('ai-chat'); svc_aiChat.register_provider({ service_name: this.service_name, @@ -21,10 +21,10 @@ class GeminiService extends BaseService { static IMPLEMENTS = { ['puter-chat-completion']: { - async models() { + async models () { return await this.models_(); }, - async list() { + async list () { const models = await this.models_(); const model_names = []; for ( const model of models ) { @@ -36,7 +36,7 @@ class GeminiService extends BaseService { return model_names; }, - async complete({ messages, stream, model, tools, max_tokens, temperature }) { + async complete ({ messages, stream, model, tools, max_tokens, temperature }) { tools = FunctionCalling.make_gemini_tools(tools); model = model ?? 'gemini-2.0-flash'; @@ -45,8 +45,8 @@ class GeminiService extends BaseService { model, tools, generationConfig: { - temperature: temperature, // Set temperature (0.0 to 1.0). Defaults to 0.7 - maxOutputTokens: max_tokens, // Note: it's maxOutputTokens, not max_tokens + temperature: temperature, // Set temperature (0.0 to 1.0). Defaults to 0.7 + maxOutputTokens: max_tokens, // Note: it's maxOutputTokens, not max_tokens }, }); @@ -111,7 +111,7 @@ class GeminiService extends BaseService { }, }; - async models_() { + async models_ () { return [ { id: 'gemini-1.5-flash', diff --git a/src/backend/src/modules/puterai/GroqAIService.js b/src/backend/src/modules/puterai/GroqAIService.js index 7d900a495c..ec1701cf27 100644 --- a/src/backend/src/modules/puterai/GroqAIService.js +++ b/src/backend/src/modules/puterai/GroqAIService.js @@ -45,7 +45,7 @@ class GroqAIService extends BaseService { * @returns {Promise} * @private */ - async _init() { + async _init () { const Groq = require('groq-sdk'); this.client = new Groq({ apiKey: this.config.apiKey, @@ -63,7 +63,7 @@ class GroqAIService extends BaseService { * Returns the default model ID for the Groq AI service * @returns {string} The default model ID 'llama-3.1-8b-instant' */ - get_default_model() { + get_default_model () { return 'llama-3.1-8b-instant'; } @@ -75,7 +75,7 @@ class GroqAIService extends BaseService { * * @returns Promise> Array of model details */ - async models() { + async models () { return await this.models_(); }, /** @@ -84,7 +84,7 @@ class GroqAIService extends BaseService { * @description Retrieves all available model IDs and their aliases, * flattening them into a single array of strings that can be used for model selection */ - async list() { + async list () { // They send: { "object": "list", data } const funny_wrapper = await this.client.models.list(); return funny_wrapper.data; @@ -97,7 +97,7 @@ class GroqAIService extends BaseService { * @param {boolean} [options.stream] - Whether to stream the response * @returns {TypedValue|Object} Returns either a TypedValue with streaming response or completion object with usage stats */ - async complete({ messages, model, stream, tools, max_tokens, temperature }) { + async complete ({ messages, model, stream, tools, max_tokens, temperature }) { model = model ?? this.get_default_model(); messages = await OpenAIUtil.process_input_messages(messages); @@ -153,7 +153,7 @@ class GroqAIService extends BaseService { * * @returns {Array} Array of model specification objects */ - models_() { + models_ () { return [ { id: 'gemma2-9b-it', diff --git a/src/backend/src/modules/puterai/MistralAIService.js b/src/backend/src/modules/puterai/MistralAIService.js index c609aa4859..1047f3da05 100644 --- a/src/backend/src/modules/puterai/MistralAIService.js +++ b/src/backend/src/modules/puterai/MistralAIService.js @@ -45,7 +45,7 @@ class MistralAIService extends BaseService { * Each model entry specifies currency (usd-cents) and costs per million tokens. * @private */ - _construct() { + _construct () { this.costs_ = { 'mistral-large-latest': { aliases: ['mistral-large-2411'], @@ -239,7 +239,7 @@ class MistralAIService extends BaseService { * Each model entry specifies currency (USD cents) and costs per million tokens. * @private */ - async _init() { + async _init () { const require = this.require; const { Mistral } = require('@mistralai/mistralai'); this.api_base_url = 'https://api.mistral.ai/v1'; @@ -265,10 +265,10 @@ class MistralAIService extends BaseService { * @private * @returns {Promise} */ - async populate_models_() { + async populate_models_ () { const resp = await axios({ method: 'get', - url: this.api_base_url + '/models', + url: `${this.api_base_url }/models`, headers: { Authorization: `Bearer ${this.config.apiKey}`, }, @@ -309,17 +309,17 @@ class MistralAIService extends BaseService { * @async * @returns {void} */ - get_default_model() { + get_default_model () { return 'mistral-large-latest'; } static IMPLEMENTS = { 'driver-capabilities': { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-ocr' && method_name === 'recognize'; }, }, 'puter-ocr': { - async recognize({ + async recognize ({ source, model, pages, @@ -383,7 +383,7 @@ class MistralAIService extends BaseService { * * @returns Promise> Array of model details */ - async models() { + async models () { return this.models_array_; }, @@ -393,7 +393,7 @@ class MistralAIService extends BaseService { * @description Retrieves all available model IDs and their aliases, * flattening them into a single array of strings that can be used for model selection */ - async list() { + async list () { return this.models_array_.map(m => m.id); }, @@ -401,7 +401,7 @@ class MistralAIService extends BaseService { * AI Chat completion method. * See AIChatService for more details. */ - async complete({ messages, stream, model, tools, max_tokens, temperature }) { + async complete ({ messages, stream, model, tools, max_tokens, temperature }) { messages = await OpenAIUtil.process_input_messages(messages); for ( const message of messages ) { @@ -466,7 +466,7 @@ class MistralAIService extends BaseService { }, }; - async _buildDocumentChunkFromSource(fileFacade) { + async _buildDocumentChunkFromSource (fileFacade) { const dataUrl = await this._safeFileValue(fileFacade, 'data_url'); const webUrl = await this._safeFileValue(fileFacade, 'web_url'); const filePath = await this._safeFileValue(fileFacade, 'path'); @@ -494,8 +494,8 @@ class MistralAIService extends BaseService { return this._chunkFromUrl(generatedDataUrl, fileName, mimeType); } - async _safeFileValue(fileFacade, key) { - if ( ! fileFacade || typeof fileFacade.get !== 'function' ) return undefined; + async _safeFileValue (fileFacade, key) { + if ( !fileFacade || typeof fileFacade.get !== 'function' ) return undefined; const maybeCache = fileFacade.values?.values; if ( maybeCache && Object.prototype.hasOwnProperty.call(maybeCache, key) ) { return maybeCache[key]; @@ -507,7 +507,7 @@ class MistralAIService extends BaseService { } } - _chunkFromUrl(url, fileName, mimeType) { + _chunkFromUrl (url, fileName, mimeType) { const lowerName = fileName?.toLowerCase(); const urlLooksPdf = /\.pdf($|\?)/i.test(url); const mimeLooksPdf = mimeType?.includes('pdf'); @@ -532,22 +532,22 @@ class MistralAIService extends BaseService { }; } - _inferMimeFromName(name) { + _inferMimeFromName (name) { if ( ! name ) return undefined; return mime.lookup(name) || undefined; } - _extractMimeFromDataUrl(url) { + _extractMimeFromDataUrl (url) { if ( typeof url !== 'string' ) return undefined; const match = url.match(/^data:([^;,]+)[;,]/); return match ? match[1] : undefined; } - _createDataUrl(buffer, mimeType) { + _createDataUrl (buffer, mimeType) { return `data:${mimeType || 'application/octet-stream'};base64,${buffer.toString('base64')}`; } - _normalizeOcrResponse(response) { + _normalizeOcrResponse (response) { if ( ! response ) return {}; const normalized = { model: response.model, @@ -577,7 +577,7 @@ class MistralAIService extends BaseService { return normalized; } - _recordOcrUsage(response, model, { annotationsRequested } = {}) { + _recordOcrUsage (response, model, { annotationsRequested } = {}) { try { if ( ! this.meteringService ) return; const actor = Context.get('actor'); @@ -594,7 +594,7 @@ class MistralAIService extends BaseService { } } - _sampleOcrResponse() { + _sampleOcrResponse () { const markdown = 'Sample OCR output (test mode).'; return { model: 'mistral-ocr-latest', diff --git a/src/backend/src/modules/puterai/OpenAIImageGenerationService.js b/src/backend/src/modules/puterai/OpenAIImageGenerationService.js index ac762c6960..e04504f893 100644 --- a/src/backend/src/modules/puterai/OpenAIImageGenerationService.js +++ b/src/backend/src/modules/puterai/OpenAIImageGenerationService.js @@ -32,7 +32,7 @@ const { Context } = require('../../util/context'); */ class OpenAIImageGenerationService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService(){ + get meteringService () { return this.services.get('meteringService').meteringService; } @@ -40,7 +40,7 @@ class OpenAIImageGenerationService extends BaseService { openai: require('openai'), }; - _construct() { + _construct () { this.models_ = { 'gpt-image-1-mini': { 'low:1024x1024': 0.005, @@ -86,12 +86,12 @@ class OpenAIImageGenerationService extends BaseService { * @async * @returns {Promise} */ - async _init() { + async _init () { let apiKey = this.config?.services?.openai?.apiKey ?? this.global_config?.services?.openai?.apiKey; - if ( !apiKey ) { + if ( ! apiKey ) { apiKey = this.config?.openai?.secret_key ?? this.global_config.openai?.secret_key; @@ -108,7 +108,7 @@ class OpenAIImageGenerationService extends BaseService { static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-image-generation' && method_name === 'generate'; }, @@ -123,7 +123,7 @@ class OpenAIImageGenerationService extends BaseService { * @returns {Promise} URL of the generated image * @throws {Error} If prompt is not a string or ratio is invalid */ - async generate(params) { + async generate (params) { const { prompt, quality, test_mode, model, ratio } = params; if ( test_mode ) { @@ -156,7 +156,7 @@ class OpenAIImageGenerationService extends BaseService { static RATIO_GPT_PORTRAIT = { w: 1024, h: 1536 }; static RATIO_GPT_LANDSCAPE = { w: 1536, h: 1024 }; - async generate(prompt, { + async generate (prompt, { ratio, model, quality, @@ -165,8 +165,8 @@ class OpenAIImageGenerationService extends BaseService { throw new Error('`prompt` must be a string'); } - if ( ! ratio || ! this._validate_ratio(ratio, model) ) { - throw new Error('`ratio` must be a valid ratio for model ' + model); + if ( !ratio || !this._validate_ratio(ratio, model) ) { + throw new Error(`\`ratio\` must be a valid ratio for model ${ model}`); } // Somewhat sane defaults @@ -176,8 +176,8 @@ class OpenAIImageGenerationService extends BaseService { if ( ! this.models_[model] ) { throw APIError.create('field_invalid', null, { key: 'model', - expected: 'one of: ' + - Object.keys(this.models_).join(', '), + expected: `one of: ${ + Object.keys(this.models_).join(', ')}`, got: model, }); } @@ -187,7 +187,7 @@ class OpenAIImageGenerationService extends BaseService { if ( quality !== undefined && !validQualities.includes(quality) ) { throw APIError.create('field_invalid', null, { key: 'quality', - expected: 'one of: ' + validQualities.join(', ').replace(/^$/, 'none (no quality)'), + expected: `one of: ${ validQualities.join(', ').replace(/^$/, 'none (no quality)')}`, got: quality, }); } @@ -198,7 +198,7 @@ class OpenAIImageGenerationService extends BaseService { const availableSizes = Object.keys(this.models_[model]); throw APIError.create('field_invalid', null, { key: 'size/quality combination', - expected: 'one of: ' + availableSizes.join(', '), + expected: `one of: ${ availableSizes.join(', ')}`, got: price_key, }); } @@ -239,12 +239,12 @@ class OpenAIImageGenerationService extends BaseService { }; if ( quality ) { - spending_meta.size = quality + ':' + spending_meta.size; + spending_meta.size = `${quality }:${ spending_meta.size}`; } - const url = result.data?.[0]?.url || (result.data?.[0]?.b64_json ? 'data:image/png;base64,' + result.data[0].b64_json : null); + const url = result.data?.[0]?.url || (result.data?.[0]?.b64_json ? `data:image/png;base64,${ result.data[0].b64_json}` : null); - if ( !url ) { + if ( ! url ) { throw new Error('Failed to extract image URL from OpenAI response'); } @@ -257,7 +257,7 @@ class OpenAIImageGenerationService extends BaseService { * @returns {Array} Array of valid quality levels * @private */ - _getValidQualities(model) { + _getValidQualities (model) { if ( model === 'gpt-image-1-mini' ) { return ['low', 'medium', 'high']; } @@ -282,7 +282,7 @@ class OpenAIImageGenerationService extends BaseService { * @returns {string} The price key * @private */ - _buildPriceKey(model, quality, size) { + _buildPriceKey (model, quality, size) { if ( model === 'gpt-image-1' || model === 'gpt-image-1-mini' ) { // gpt-image-1 and gpt-image-1-mini use format: "quality:size" - default to low if not specified const qualityLevel = quality || 'low'; @@ -300,7 +300,7 @@ class OpenAIImageGenerationService extends BaseService { * @returns {Object} API parameters object * @private */ - _buildApiParams(model, baseParams) { + _buildApiParams (model, baseParams) { const apiParams = { user: baseParams.user, prompt: baseParams.prompt, @@ -329,7 +329,7 @@ class OpenAIImageGenerationService extends BaseService { * @returns {Array} Array of valid ratio objects * @private */ - _getValidRatios(model) { + _getValidRatios (model) { const commonRatios = [this.constructor.RATIO_SQUARE]; if ( model === 'gpt-image-1' || model === 'gpt-image-1-mini' ) { @@ -348,7 +348,7 @@ class OpenAIImageGenerationService extends BaseService { } } - _validate_ratio(ratio, model) { + _validate_ratio (ratio, model) { const validRatios = this._getValidRatios(model); return validRatios.includes(ratio); } diff --git a/src/backend/src/modules/puterai/OpenAISpeechToTextService.js b/src/backend/src/modules/puterai/OpenAISpeechToTextService.js index f53d50b2ca..d70e26fc59 100644 --- a/src/backend/src/modules/puterai/OpenAISpeechToTextService.js +++ b/src/backend/src/modules/puterai/OpenAISpeechToTextService.js @@ -64,7 +64,7 @@ const TRANSCRIPTION_MODEL_CAPABILITIES = { class OpenAISpeechToTextService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService() { + get meteringService () { return this.services.get('meteringService').meteringService; } @@ -75,12 +75,12 @@ class OpenAISpeechToTextService extends BaseService { path: require('path'), }; - async _init() { + async _init () { let apiKey = this.config?.services?.openai?.apiKey ?? this.global_config?.services?.openai?.apiKey; - if ( !apiKey ) { + if ( ! apiKey ) { apiKey = this.config?.openai?.secret_key ?? this.global_config.openai?.secret_key; @@ -91,7 +91,7 @@ class OpenAISpeechToTextService extends BaseService { } } - if ( !apiKey ) { + if ( ! apiKey ) { throw new Error('OpenAI API key not configured'); } @@ -100,25 +100,25 @@ class OpenAISpeechToTextService extends BaseService { static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-speech2txt' && (method_name === 'transcribe' || method_name === 'translate'); }, }, ['puter-speech2txt']: { - async list_models() { + async list_models () { return this.listModels(); }, - async transcribe(params) { + async transcribe (params) { return this._handleTranscription({ ...params, translate: false }); }, - async translate(params) { + async translate (params) { return this._handleTranscription({ ...params, translate: true }); }, }, }; - listModels() { + listModels () { return [ { id: 'gpt-4o-mini-transcribe', @@ -157,7 +157,7 @@ class OpenAISpeechToTextService extends BaseService { ]; } - async _handleTranscription({ + async _handleTranscription ({ file, translate = false, model, @@ -187,7 +187,7 @@ class OpenAISpeechToTextService extends BaseService { }); } - if ( !file ) { + if ( ! file ) { throw APIError.create('field_missing', null, { key: 'file' }); } @@ -208,7 +208,7 @@ class OpenAISpeechToTextService extends BaseService { const selectedModel = model || (translate ? DEFAULT_TRANSLATE_MODEL : DEFAULT_TRANSCRIBE_MODEL); const capabilities = TRANSCRIPTION_MODEL_CAPABILITIES[selectedModel]; - if ( !capabilities ) { + if ( ! capabilities ) { throw APIError.create('field_invalid', null, { key: 'model', expected: Object.keys(TRANSCRIPTION_MODEL_CAPABILITIES).join(', '), @@ -241,13 +241,13 @@ class OpenAISpeechToTextService extends BaseService { if ( timestamp_granularities && !capabilities.timestampGranularities ) { throw APIError.create('field_invalid', null, { key: 'timestamp_granularities', - expected: `Only supported on models that provide timestamp granularity (such as whisper-1).`, + expected: 'Only supported on models that provide timestamp granularity (such as whisper-1).', }); } let diarizationChunkingStrategy = chunking_strategy; if ( capabilities.diarization ) { - if ( !response_format ) { + if ( ! response_format ) { response_format = 'diarized_json'; } if ( !diarizationChunkingStrategy && capabilities.requiresChunkingOverThirtySeconds && estimatedSeconds > 30 ) { @@ -259,15 +259,13 @@ class OpenAISpeechToTextService extends BaseService { const usageType = `openai:${selectedModel}:second`; const usageAllowed = await this.meteringService.hasEnoughCreditsFor(actor, usageType, estimatedSeconds); - if ( !usageAllowed ) { + if ( ! usageAllowed ) { throw APIError.create('insufficient_funds'); } - const openaiFile = await this.modules.openai.toFile( - buffer, - filename, - mimeType ? { type: mimeType } : undefined, - ); + const openaiFile = await this.modules.openai.toFile(buffer, + filename, + mimeType ? { type: mimeType } : undefined); const payload = { file: openaiFile, @@ -304,7 +302,7 @@ class OpenAISpeechToTextService extends BaseService { return this._formatResponse(transcription, response_format); } - async _prepareAudioBuffer(file) { + async _prepareAudioBuffer (file) { const buffer = await file.get('buffer'); if ( !buffer || !buffer.length ) { throw APIError.create('field_invalid', null, { @@ -346,14 +344,14 @@ class OpenAISpeechToTextService extends BaseService { } } - if ( !mimeType ) { + if ( ! mimeType ) { const guessedMime = this.modules.mime.lookup(filename); if ( guessedMime ) { mimeType = guessedMime; } } - if ( !filename.includes('.') ) { + if ( ! filename.includes('.') ) { const extension = mimeType ? this.modules.mime.extension(mimeType) : 'mp3'; filename = `${filename}.${extension || 'mp3'}`; } @@ -384,7 +382,7 @@ class OpenAISpeechToTextService extends BaseService { }; } - _formatResponse(result, response_format) { + _formatResponse (result, response_format) { if ( response_format === 'text' && typeof result === 'string' ) { return result; } diff --git a/src/backend/src/modules/puterai/OpenAITTSService.js b/src/backend/src/modules/puterai/OpenAITTSService.js index 40b27271d4..68e8f656c8 100644 --- a/src/backend/src/modules/puterai/OpenAITTSService.js +++ b/src/backend/src/modules/puterai/OpenAITTSService.js @@ -74,7 +74,7 @@ const OPENAI_TTS_MODELS = [ */ class OpenAITTSService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService() { + get meteringService () { return this.services.get('meteringService').meteringService; } @@ -82,12 +82,12 @@ class OpenAITTSService extends BaseService { openai: require('openai'), }; - async _init() { + async _init () { let apiKey = this.config?.services?.openai?.apiKey ?? this.global_config?.services?.openai?.apiKey; - if ( !apiKey ) { + if ( ! apiKey ) { apiKey = this.config?.openai?.secret_key ?? this.global_config.openai?.secret_key; @@ -98,7 +98,7 @@ class OpenAITTSService extends BaseService { } } - if ( !apiKey ) { + if ( ! apiKey ) { throw new Error('OpenAI API key not configured'); } @@ -107,12 +107,12 @@ class OpenAITTSService extends BaseService { static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-tts' && method_name === 'synthesize'; }, }, ['puter-tts']: { - async list_voices({ provider } = {}) { + async list_voices ({ provider } = {}) { if ( provider && provider !== 'openai' ) { return []; } @@ -128,7 +128,7 @@ class OpenAITTSService extends BaseService { supported_models: OPENAI_TTS_MODELS.map(model => model.id), })); }, - async list_engines({ provider } = {}) { + async list_engines ({ provider } = {}) { if ( provider && provider !== 'openai' ) { return []; } @@ -140,13 +140,13 @@ class OpenAITTSService extends BaseService { provider: 'openai', })); }, - async synthesize(params) { + async synthesize (params) { return this.synthesize(params); }, }, }; - async synthesize({ + async synthesize ({ text, voice, model, @@ -166,7 +166,7 @@ class OpenAITTSService extends BaseService { } model = model || DEFAULT_MODEL; - if ( !OPENAI_TTS_MODELS.find(({ id }) => id === model) ) { + if ( ! OPENAI_TTS_MODELS.find(({ id }) => id === model) ) { throw APIError.create('field_invalid', null, { key: 'model', expected: OPENAI_TTS_MODELS.map(({ id }) => id).join(', '), @@ -175,7 +175,7 @@ class OpenAITTSService extends BaseService { } voice = voice || DEFAULT_VOICE; - if ( !OPENAI_TTS_VOICES.find(({ id }) => id === voice) ) { + if ( ! OPENAI_TTS_VOICES.find(({ id }) => id === voice) ) { throw APIError.create('field_invalid', null, { key: 'voice', expected: OPENAI_TTS_VOICES.map(({ id }) => id).join(', '), @@ -190,7 +190,7 @@ class OpenAITTSService extends BaseService { const usageType = `openai:${model}:character`; const usageAllowed = await this.meteringService.hasEnoughCreditsFor(actor, usageType, text.length); - if ( !usageAllowed ) { + if ( ! usageAllowed ) { throw APIError.create('insufficient_funds'); } diff --git a/src/backend/src/modules/puterai/OpenAIVideoGenerationService.js b/src/backend/src/modules/puterai/OpenAIVideoGenerationService.js index 97f2c90956..ee2b135ad3 100644 --- a/src/backend/src/modules/puterai/OpenAIVideoGenerationService.js +++ b/src/backend/src/modules/puterai/OpenAIVideoGenerationService.js @@ -34,7 +34,7 @@ const ALLOWED_SECONDS = new Set(['4', '8', '12']); class OpenAIVideoGenerationService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService(){ + get meteringService () { return this.services.get('meteringService').meteringService; } @@ -42,7 +42,7 @@ class OpenAIVideoGenerationService extends BaseService { openai: require('openai'), }; - _construct() { + _construct () { this.models_ = { 'sora-2': { defaultUsageKey: 'openai:sora-2:default', @@ -53,12 +53,12 @@ class OpenAIVideoGenerationService extends BaseService { }; } - async _init() { + async _init () { let apiKey = this.config?.services?.openai?.apiKey ?? this.global_config?.services?.openai?.apiKey; - if ( !apiKey ) { + if ( ! apiKey ) { apiKey = this.config?.openai?.secret_key ?? this.global_config.openai?.secret_key; @@ -74,19 +74,19 @@ class OpenAIVideoGenerationService extends BaseService { static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-video-generation' && method_name === 'generate'; }, }, ['puter-video-generation']: { - async generate(params) { + async generate (params) { return await this.generateVideo(params); }, }, }; - async generateVideo(params) { + async generateVideo (params) { const { prompt, model: requestedModel, @@ -108,10 +108,10 @@ class OpenAIVideoGenerationService extends BaseService { const model = requestedModel ?? 'sora-2'; const modelConfig = this.models_[model]; - if ( !modelConfig ) { + if ( ! modelConfig ) { throw APIError.create('field_invalid', null, { key: 'model', - expected: 'one of: ' + Object.keys(this.models_).join(', '), + expected: `one of: ${ Object.keys(this.models_).join(', ')}`, got: model, }); } @@ -127,14 +127,14 @@ class OpenAIVideoGenerationService extends BaseService { const normalizedSeconds = this.#normalizeSeconds(seconds ?? duration) ?? '4'; const usageKey = this.#determineUsageKey(model, normalizedSize); - if ( !usageKey ) { + if ( ! usageKey ) { throw new Error(`Unsupported pricing tier for model ${model}`); } const estimatedUnits = this.#parseSeconds(normalizedSeconds) ?? DEFAULT_DURATION_SECONDS; const actor = Context.get('actor'); const usageAllowed = await this.meteringService.hasEnoughCreditsFor(actor, usageKey, estimatedUnits); - if ( !usageAllowed ) { + if ( ! usageAllowed ) { throw APIError.create('insufficient_funds'); } @@ -159,7 +159,7 @@ class OpenAIVideoGenerationService extends BaseService { const finalResolution = this.#normalizeSize(finalJob.size) ?? normalizedSize; const finalUsageKey = this.#determineUsageKey(model, finalResolution); - if ( !finalUsageKey ) { + if ( ! finalUsageKey ) { throw new Error(`Unsupported pricing tier for model ${model}`); } @@ -173,7 +173,7 @@ class OpenAIVideoGenerationService extends BaseService { stream = Readable.fromWeb(stream); } - if ( !stream ) { + if ( ! stream ) { const arrayBuffer = await downloadResponse.arrayBuffer(); stream = Readable.from(Buffer.from(arrayBuffer)); } @@ -186,7 +186,7 @@ class OpenAIVideoGenerationService extends BaseService { }, stream); } - async #pollUntilComplete(initialJob) { + async #pollUntilComplete (initialJob) { let job = initialJob; const start = Date.now(); @@ -202,12 +202,12 @@ class OpenAIVideoGenerationService extends BaseService { return job; } - async #delay(ms) { + async #delay (ms) { return await new Promise(resolve => setTimeout(resolve, ms)); } - #normalizeSize(candidate) { - if ( !candidate ) return undefined; + #normalizeSize (candidate) { + if ( ! candidate ) return undefined; const normalized = this.#normalizeResolution(candidate); if ( normalized && ALLOWED_SIZES.has(normalized) ) { return normalized; @@ -215,7 +215,7 @@ class OpenAIVideoGenerationService extends BaseService { return undefined; } - #normalizeSeconds(value) { + #normalizeSeconds (value) { if ( value === null || value === undefined ) { return undefined; } @@ -240,9 +240,9 @@ class OpenAIVideoGenerationService extends BaseService { return undefined; } - #determineUsageKey(model, normalizedSize) { + #determineUsageKey (model, normalizedSize) { const config = this.models_[model]; - if ( !config ) return null; + if ( ! config ) return null; if ( model === 'sora-2-pro' && normalizedSize === '1792x1024' ) { return 'openai:sora-2-pro:xl'; @@ -251,8 +251,8 @@ class OpenAIVideoGenerationService extends BaseService { return config.defaultUsageKey; } - #normalizeResolution(value) { - if ( !value ) return undefined; + #normalizeResolution (value) { + if ( ! value ) return undefined; if ( typeof value === 'string' ) { const match = value.match(/(\\d+)\\s*x\\s*(\\d+)/i); if ( match ) { @@ -268,7 +268,7 @@ class OpenAIVideoGenerationService extends BaseService { return undefined; } - #parseSeconds(value) { + #parseSeconds (value) { if ( value === null || value === undefined ) return undefined; if ( typeof value === 'number' && Number.isFinite(value) ) { return value; diff --git a/src/backend/src/modules/puterai/PuterAIModule.js b/src/backend/src/modules/puterai/PuterAIModule.js index 361ccbcede..bbd3e44ff5 100644 --- a/src/backend/src/modules/puterai/PuterAIModule.js +++ b/src/backend/src/modules/puterai/PuterAIModule.js @@ -18,8 +18,8 @@ */ // METADATA // {"ai-commented":{"service":"claude"}} -const { AdvancedBase } = require("@heyputer/putility"); -const config = require("../../config"); +const { AdvancedBase } = require('@heyputer/putility'); +const config = require('../../config'); /** * PuterAIModule class extends AdvancedBase to manage and register various AI services. @@ -36,7 +36,7 @@ class PuterAIModule extends AdvancedBase { * Extends AdvancedBase to provide core functionality * Handles registration and configuration of various AI services like OpenAI, Claude, AWS services etc. */ - async install(context) { + async install (context) { const services = context.get('services'); const { AIInterfaceService } = require('./AIInterfaceService'); @@ -132,7 +132,7 @@ class PuterAIModule extends AdvancedBase { }); // User can disable ollama in the config, but by default it should be enabled if discovery is successful if ( ollama_available || config?.services?.['ollama']?.enabled ) { - console.log("Local AI support detected! Registering Ollama"); + console.log('Local AI support detected! Registering Ollama'); const { OllamaService } = require('./OllamaService'); services.registerService('ollama', OllamaService); } diff --git a/src/backend/src/modules/puterai/TogetherImageGenerationService.js b/src/backend/src/modules/puterai/TogetherImageGenerationService.js index 1205f45768..9baab606e5 100644 --- a/src/backend/src/modules/puterai/TogetherImageGenerationService.js +++ b/src/backend/src/modules/puterai/TogetherImageGenerationService.js @@ -32,18 +32,18 @@ const { Together } = require('together-ai'); */ class TogetherImageGenerationService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService() { + get meteringService () { return this.services.get('meteringService').meteringService; } static MODULES = {}; - async _init() { + async _init () { const apiKey = this.config?.apiKey ?? this.global_config?.services?.['together-ai']?.apiKey; - if ( !apiKey ) { + if ( ! apiKey ) { throw new Error('Together AI image generation requires an API key'); } @@ -52,7 +52,7 @@ class TogetherImageGenerationService extends BaseService { static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-image-generation' && method_name === 'generate'; }, @@ -77,7 +77,7 @@ class TogetherImageGenerationService extends BaseService { * @param {boolean} [params.test_mode] - Enable Puter test mode shortcut * @returns {Promise} TypedValue containing the generated image URL or data URI */ - async generate(params) { + async generate (params) { const { prompt, test_mode, @@ -146,7 +146,7 @@ class TogetherImageGenerationService extends BaseService { * Generates an image using Together AI client * @private */ - async generate(prompt, options) { + async generate (prompt, options) { if ( typeof prompt !== 'string' || prompt.trim().length === 0 ) { throw new Error('`prompt` must be a non-empty string'); } @@ -154,18 +154,18 @@ class TogetherImageGenerationService extends BaseService { const request = this._buildRequest(prompt, options); const actor = Context.get('actor'); - if ( !actor ) { + if ( ! actor ) { throw new Error('actor not found in context'); } const usageType = `together-image:${request.model}`; const usageAllowed = await this.meteringService.hasEnoughCreditsFor(actor, usageType, 1); - if ( !usageAllowed ) { + if ( ! usageAllowed ) { throw APIError.create('insufficient_funds'); } const response = await this.client.images.create(request); - if ( !response?.data?.length ) { + if ( ! response?.data?.length ) { throw new Error('Together AI response did not include image data'); } @@ -176,7 +176,7 @@ class TogetherImageGenerationService extends BaseService { return first.url; } if ( first.b64_json ) { - return 'data:image/png;base64,' + first.b64_json; + return `data:image/png;base64,${ first.b64_json}`; } throw new Error('Together AI response did not include an image URL'); @@ -186,7 +186,7 @@ class TogetherImageGenerationService extends BaseService { * Normalizes Together AI image generation request parameters * @private */ - _buildRequest(prompt, options = {}) { + _buildRequest (prompt, options = {}) { const { ratio, model, @@ -217,12 +217,8 @@ class TogetherImageGenerationService extends BaseService { const ratioWidth = (ratio && ratio.w !== undefined) ? Number(ratio.w) : undefined; const ratioHeight = (ratio && ratio.h !== undefined) ? Number(ratio.h) : undefined; - const normalizedWidth = this._normalizeDimension( - width !== undefined ? Number(width) : (ratioWidth ?? this.constructor.DEFAULT_RATIO.w) - ); - const normalizedHeight = this._normalizeDimension( - height !== undefined ? Number(height) : (ratioHeight ?? this.constructor.DEFAULT_RATIO.h) - ); + const normalizedWidth = this._normalizeDimension(width !== undefined ? Number(width) : (ratioWidth ?? this.constructor.DEFAULT_RATIO.w)); + const normalizedHeight = this._normalizeDimension(height !== undefined ? Number(height) : (ratioHeight ?? this.constructor.DEFAULT_RATIO.h)); if ( aspect_ratio ) { request.aspect_ratio = aspect_ratio; @@ -260,7 +256,7 @@ class TogetherImageGenerationService extends BaseService { ? resolvedImageBase64 : (typeof image_url === 'string' ? image_url : undefined); - if ( !conditionSource ) { + if ( ! conditionSource ) { throw new Error(`Model ${request.model} requires an image_url or image_base64 input`); } @@ -270,14 +266,14 @@ class TogetherImageGenerationService extends BaseService { return request; } - _normalizeDimension(value) { + _normalizeDimension (value) { if ( typeof value !== 'number' ) return undefined; const rounded = Math.max(64, Math.round(value)); // Flux models expect multiples of 8. Snap to the nearest multiple without going below 64. return Math.max(64, Math.round(rounded / 8) * 8); } - static _modelRequiresConditionImage(model) { + static _modelRequiresConditionImage (model) { if ( typeof model !== 'string' || model.trim() === '' ) { return false; } diff --git a/src/backend/src/modules/puterai/TogetherVideoGenerationService.js b/src/backend/src/modules/puterai/TogetherVideoGenerationService.js index 84c2886e9c..0aa26e02d8 100644 --- a/src/backend/src/modules/puterai/TogetherVideoGenerationService.js +++ b/src/backend/src/modules/puterai/TogetherVideoGenerationService.js @@ -33,18 +33,18 @@ const DEFAULT_USAGE_KEY = 'together-video:default'; class TogetherVideoGenerationService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ - get meteringService() { + get meteringService () { return this.services.get('meteringService').meteringService; } static MODULES = {}; - async _init() { + async _init () { const apiKey = this.config?.apiKey ?? this.global_config?.services?.['together-ai']?.apiKey; - if ( !apiKey ) { + if ( ! apiKey ) { throw new Error('Together AI video generation requires an API key'); } @@ -53,19 +53,19 @@ class TogetherVideoGenerationService extends BaseService { static IMPLEMENTS = { ['driver-capabilities']: { - supports_test_mode(iface, method_name) { + supports_test_mode (iface, method_name) { return iface === 'puter-video-generation' && method_name === 'generate'; }, }, ['puter-video-generation']: { - async generate(params) { + async generate (params) { return await this.generateVideo(params); }, }, }; - async generateVideo(params) { + async generateVideo (params) { const { prompt, model: requestedModel, @@ -106,7 +106,7 @@ class TogetherVideoGenerationService extends BaseService { const normalizedSeconds = this.#coercePositiveInteger(seconds ?? duration) ?? DEFAULT_DURATION_SECONDS; const actor = Context.get('actor'); - if ( !actor ) { + if ( ! actor ) { throw new Error('actor not found in context'); } @@ -114,7 +114,7 @@ class TogetherVideoGenerationService extends BaseService { const usageKey = this.#determineUsageKey(model); const usageAllowed = await this.meteringService.hasEnoughCreditsFor(actor, usageKey, estimatedUsageUnits); - if ( !usageAllowed ) { + if ( ! usageAllowed ) { throw APIError.create('insufficient_funds'); } @@ -191,7 +191,7 @@ class TogetherVideoGenerationService extends BaseService { throw new Error('Together AI response did not include a video URL'); } - async #pollUntilComplete(jobId) { + async #pollUntilComplete (jobId) { let job = await this.client.videos.retrieve(jobId); const start = Date.now(); @@ -207,18 +207,18 @@ class TogetherVideoGenerationService extends BaseService { return job; } - async #delay(ms) { + async #delay (ms) { return await new Promise(resolve => setTimeout(resolve, ms)); } - #determineUsageKey(model) { + #determineUsageKey (model) { if ( typeof model === 'string' && model.trim() ) { return `together-video:${model}`; } return DEFAULT_USAGE_KEY; } - #coercePositiveInteger(value) { + #coercePositiveInteger (value) { if ( typeof value === 'number' && Number.isFinite(value) ) { const rounded = Math.round(value); return rounded > 0 ? rounded : undefined; @@ -230,7 +230,7 @@ class TogetherVideoGenerationService extends BaseService { return undefined; } - #isFiniteNumber(value) { + #isFiniteNumber (value) { if ( typeof value === 'number' ) { return Number.isFinite(value); } diff --git a/src/backend/src/modules/puterai/UsageLimitedChatService.js b/src/backend/src/modules/puterai/UsageLimitedChatService.js index c23fdf2444..0df931ee0f 100644 --- a/src/backend/src/modules/puterai/UsageLimitedChatService.js +++ b/src/backend/src/modules/puterai/UsageLimitedChatService.js @@ -30,7 +30,7 @@ const Streaming = require('./lib/Streaming'); * Can handle both streaming and non-streaming requests consistently. */ class UsageLimitedChatService extends BaseService { - get_default_model() { + get_default_model () { return 'usage-limited'; } @@ -40,7 +40,7 @@ class UsageLimitedChatService extends BaseService { * Returns a list of available model names * @returns {Promise} Array containing the single model identifier */ - async list() { + async list () { return ['usage-limited']; }, @@ -48,7 +48,7 @@ class UsageLimitedChatService extends BaseService { * Returns model details for the usage-limited model * @returns {Promise} Array containing the model details */ - async models() { + async models () { return [{ id: 'usage-limited', name: 'Usage Limited', @@ -70,7 +70,7 @@ class UsageLimitedChatService extends BaseService { * @param {string} params.model - The model to use (unused) * @returns {Object|TypedValue} A chat completion response or streamed response */ - async complete({ stream, customLimitMessage }) { + async complete ({ stream, customLimitMessage }) { const limitMessage = customLimitMessage || dedent(` You have reached your AI usage limit for this account. `); diff --git a/src/backend/src/modules/puterai/XAIService.js b/src/backend/src/modules/puterai/XAIService.js index 5e704d03be..cdb9b77045 100644 --- a/src/backend/src/modules/puterai/XAIService.js +++ b/src/backend/src/modules/puterai/XAIService.js @@ -36,7 +36,7 @@ class XAIService extends BaseService { /** @type {import('../../services/MeteringService/MeteringService').MeteringService} */ meteringService; - adapt_model(model) { + adapt_model (model) { return model; } @@ -45,7 +45,7 @@ class XAIService extends BaseService { * @private * @returns {Promise} Resolves when initialization is complete */ - async _init() { + async _init () { this.openai = new this.modules.openai.OpenAI({ apiKey: this.global_config.services.xai.apiKey, baseURL: 'https://api.x.ai/v1', @@ -63,7 +63,7 @@ class XAIService extends BaseService { * Returns the default model identifier for the XAI service * @returns {string} The default model ID 'grok-beta' */ - get_default_model() { + get_default_model () { return 'grok-beta'; } @@ -75,7 +75,7 @@ class XAIService extends BaseService { * * @returns Array Array of model details */ - models() { + models () { return this.models_(); }, /** @@ -84,7 +84,7 @@ class XAIService extends BaseService { * @description Retrieves all available model IDs and their aliases, * flattening them into a single array of strings that can be used for model selection */ - async list() { + async list () { const models = await this.models_(); const model_names = []; for ( const model of models ) { @@ -100,7 +100,7 @@ class XAIService extends BaseService { * AI Chat completion method. * See AIChatService for more details. */ - async complete({ messages, stream, model, tools }) { + async complete ({ messages, stream, model, tools }) { model = this.adapt_model(model); messages = await OpenAIUtil.process_input_messages(messages); @@ -151,7 +151,7 @@ class XAIService extends BaseService { * - cost: Pricing information object with currency and rates * @private */ - models_() { + models_ () { return [ { id: 'grok-beta', diff --git a/src/backend/src/modules/puterai/experiment/stream_claude.js b/src/backend/src/modules/puterai/experiment/stream_claude.js index 7475207ffc..e63379dc53 100644 --- a/src/backend/src/modules/puterai/experiment/stream_claude.js +++ b/src/backend/src/modules/puterai/experiment/stream_claude.js @@ -6,14 +6,14 @@ const claude_sample = require('../samples/claude-tools-1'); const echo_stream = { write: data => { console.log(data); - } + }, }; const chatStream = new Streaming.AIChatStream({ stream: echo_stream }); let message; let contentBlock; -for (const event of claude_sample) { +for ( const event of claude_sample ) { if ( event.type === 'message_start' ) { message = chatStream.message(); continue; diff --git a/src/backend/src/modules/puterai/experiment/stream_openai.js b/src/backend/src/modules/puterai/experiment/stream_openai.js index a863d8e69d..ef651ca5d0 100644 --- a/src/backend/src/modules/puterai/experiment/stream_openai.js +++ b/src/backend/src/modules/puterai/experiment/stream_openai.js @@ -6,10 +6,9 @@ const openai_fish = require('../samples/openai-tools-1'); const echo_stream = { write: data => { console.log(data); - } + }, }; - const chatStream = new Streaming.AIChatStream({ stream: echo_stream, }); @@ -37,7 +36,7 @@ for ( const chunk of openai_fish ) { continue; } - if ( ! nou(choice.delta.tool_calls) ) { + if ( ! nou(choice.delta.tool_calls) ) { if ( mode === 'text' ) { mode = 'tool'; textblock.end(); diff --git a/src/backend/src/modules/puterai/lib/AsModeration.js b/src/backend/src/modules/puterai/lib/AsModeration.js index c057791661..7b58d63c18 100644 --- a/src/backend/src/modules/puterai/lib/AsModeration.js +++ b/src/backend/src/modules/puterai/lib/AsModeration.js @@ -1,30 +1,30 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { default: dedent } = require("dedent"); +const { default: dedent } = require('dedent'); class AsModeration { constructor ({ chat, model }) { this.chat = chat; this.model = model; } - + async moderate (text) { // copied from Anthropic documentation // https://docs.anthropic.com/en/docs/about-claude/use-case-guides/content-moderation @@ -32,19 +32,19 @@ class AsModeration { 'Child Exploitation', 'Conspiracy Theories', 'Hate', - 'Indiscriminate Weapons', + 'Indiscriminate Weapons', 'Intellectual Property', - 'Non-Violent Crimes', + 'Non-Violent Crimes', 'Privacy', 'Self-Harm', 'Sex Crimes', 'Sexual Content', 'Specialized Advice', - 'Violent Crimes' + 'Violent Crimes', ]; - + const unsafe_category_str = unsafe_categories.join('\n'); - + const assessment_prompt = dedent(` Determine whether the following message warrants moderation, based on the unsafe categories outlined below. @@ -52,8 +52,8 @@ class AsModeration { Message: ${text - .replace('<', '<') - .replace('>', '>') + .replace('<', '<') + .replace('>', '>') } @@ -69,24 +69,24 @@ class AsModeration { "explanation": [Optional. Only include if there is a violation.] }} `); - + const result = await this.chat.complete({ messages: [ { role: 'user', content: assessment_prompt, - } - ] + }, + ], }); - + console.log('result???', require('util').inspect(result, { depth: null })); - + const str = result.message?.content?.[0]?.text ?? result.messages?.[0]?.content?.[0]?.text ?? '{ "violation": true }'; - + const parsed = JSON.parse(str); - return ! parsed.violation; + return !parsed.violation; } } diff --git a/src/backend/src/modules/puterai/lib/FunctionCalling.js b/src/backend/src/modules/puterai/lib/FunctionCalling.js index 0b9fe4aff2..ef2ad1217f 100644 --- a/src/backend/src/modules/puterai/lib/FunctionCalling.js +++ b/src/backend/src/modules/puterai/lib/FunctionCalling.js @@ -1,20 +1,20 @@ module.exports = class FunctionCalling { /** * Normalizes the 'tools' object in-place. - * + * * This function will accept an array of tools provided by the * user, and produce a normalized object that can then be * converted to the apprpriate representation for another * service. - * + * * We will accept conventions from either service that a user * might expect to work, prioritizing the OpenAI convention * when conflicting conventions are present. - * - * @param {*} tools + * + * @param {*} tools */ static normalize_tools_object (tools) { - for ( let i=0 ; i < tools.length ; i++ ) { + for ( let i = 0 ; i < tools.length ; i++ ) { const tool = tools[i]; let normalized_tool = {}; @@ -23,7 +23,7 @@ module.exports = class FunctionCalling { let parameters = fn.parameters || fn.input_schema; - + normal_fn.parameters = parameters ?? { type: 'object', }; @@ -41,7 +41,7 @@ module.exports = class FunctionCalling { } return normal_fn; - } + }; if ( tool.input_schema ) { normalized_tool = { @@ -52,7 +52,7 @@ module.exports = class FunctionCalling { normalized_tool = { type: 'function', function: normalize_function(tool.function), - } + }; } else { normalized_tool = { type: 'function', @@ -93,9 +93,9 @@ module.exports = class FunctionCalling { /** * This function will convert a normalized tools object to the * format expected by OpenAI. - * - * @param {*} tools - * @returns + * + * @param {*} tools + * @returns */ static make_openai_tools (tools) { return tools; @@ -104,9 +104,9 @@ module.exports = class FunctionCalling { /** * This function will convert a normalized tools object to the * format expected by Claude. - * - * @param {*} tools - * @returns + * + * @param {*} tools + * @returns */ static make_claude_tools (tools) { if ( ! tools ) return undefined; @@ -121,17 +121,17 @@ module.exports = class FunctionCalling { } static make_gemini_tools (tools) { - if (Array.isArray(tools)) { + if ( Array.isArray(tools) ) { return [ { function_declarations: tools.map(t => { const tool = t.function; delete tool.parameters.additionalProperties; return tool; - }) - } + }), + }, ]; }; } -} +}; diff --git a/src/backend/src/modules/puterai/lib/Messages.js b/src/backend/src/modules/puterai/lib/Messages.js index edd3ffcd79..16c6307c59 100644 --- a/src/backend/src/modules/puterai/lib/Messages.js +++ b/src/backend/src/modules/puterai/lib/Messages.js @@ -1,11 +1,11 @@ -const { whatis } = require("../../../util/langutil"); +const { whatis } = require('../../../util/langutil'); module.exports = class Messages { /** * Normalizes a single message into a standardized format with role and content array. * Converts string messages to objects, ensures content is an array of content blocks, * transforms tool_calls into tool_use content blocks, and coerces content items into objects. - * + * * @param {string|Object} message - The message to normalize, either a string or message object * @param {Object} params - Optional parameters including default role * @returns {Object} Normalized message with role and content array @@ -32,7 +32,7 @@ module.exports = class Messages { if ( ! message.content ) { if ( message.tool_calls ) { message.content = []; - for ( let i=0 ; i < message.tool_calls.length ; i++ ) { + for ( let i = 0 ; i < message.tool_calls.length ; i++ ) { const tool_call = message.tool_calls[i]; message.content.push({ type: 'tool_use', @@ -43,14 +43,14 @@ module.exports = class Messages { } delete message.tool_calls; } else { - throw new Error(`each message must have a 'content' property`); + throw new Error('each message must have a \'content\' property'); } } if ( whatis(message.content) !== 'array' ) { message.content = [message.content]; } // Coerce each content block into an object - for ( let i=0 ; i < message.content.length ; i++ ) { + for ( let i = 0 ; i < message.content.length ; i++ ) { if ( whatis(message.content[i]) === 'string' ) { message.content[i] = { type: 'text', @@ -60,13 +60,13 @@ module.exports = class Messages { if ( whatis(message.content[i]) !== 'object' ) { throw new Error('each message content item must be a string or object'); } - if ( typeof message.content[i].text === 'string' && ! message.content[i].type ) { + if ( typeof message.content[i].text === 'string' && !message.content[i].type ) { message.content[i].type = 'text'; } } // Remove "text" properties from content blocks with type=tool_result - for ( let i=0 ; i < message.content.length ; i++ ) { + for ( let i = 0 ; i < message.content.length ; i++ ) { if ( message.content[i].type !== 'tool_use' ) { continue; } @@ -82,23 +82,23 @@ module.exports = class Messages { * Normalizes an array of messages by applying normalize_single_message to each, * then splits messages with multiple content blocks into separate messages, * and finally merges consecutive messages from the same role. - * + * * @param {Array} messages - Array of messages to normalize * @param {Object} params - Optional parameters passed to normalize_single_message * @returns {Array} Normalized and merged array of messages */ static normalize_messages (messages, params = {}) { - for ( let i=0 ; i < messages.length ; i++ ) { + for ( let i = 0 ; i < messages.length ; i++ ) { messages[i] = this.normalize_single_message(messages[i], params); } // Split messages with tool_use content into separate messages // TODO: unit test this messages = [...messages]; - for ( let i=0 ; i < messages.length ; i++ ) { + for ( let i = 0 ; i < messages.length ; i++ ) { let message = messages[i]; let separated_messages = []; - for ( let j=0 ; j < message.content.length ; j++ ) { + for ( let j = 0 ; j < message.content.length ; j++ ) { if ( message.content[j].type === 'tool_result' ) { separated_messages.push({ ...message, @@ -117,7 +117,7 @@ module.exports = class Messages { // If multiple messages are from the same role, merge them let merged_messages = []; let current_role = null; - for ( let i=0 ; i < messages.length ; i++ ) { + for ( let i = 0 ; i < messages.length ; i++ ) { if ( current_role === messages[i].role ) { merged_messages[merged_messages.length - 1].content.push(...messages[i].content); } else { @@ -131,14 +131,14 @@ module.exports = class Messages { /** * Separates system messages from other messages in the array. - * + * * @param {Array} messages - Array of messages to process * @returns {Array} Tuple containing [system_messages, non_system_messages] */ static extract_and_remove_system_messages (messages) { let system_messages = []; let new_messages = []; - for ( let i=0 ; i < messages.length ; i++ ) { + for ( let i = 0 ; i < messages.length ; i++ ) { if ( messages[i].role === 'system' ) { system_messages.push(messages[i]); } else { @@ -152,7 +152,7 @@ module.exports = class Messages { * Extracts all text content from messages, handling various message formats. * Processes strings, objects with content arrays, and nested content structures, * joining all text with spaces. - * + * * @param {Array} messages - Array of messages to extract text from * @returns {string} Concatenated text content from all messages * @throws {Error} If text content is not a string @@ -172,7 +172,7 @@ module.exports = class Messages { return m.content; } else { const is_text_type = m.content.type === 'text' || - ! m.content.hasOwnProperty('type'); + !m.content.hasOwnProperty('type'); if ( is_text_type ) { if ( whatis(m.content.text) !== 'string' ) { throw new Error('text content must be a string'); @@ -183,4 +183,4 @@ module.exports = class Messages { } }).join(' '); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/src/backend/src/modules/puterai/lib/Streaming.js b/src/backend/src/modules/puterai/lib/Streaming.js index 933c078b5d..e13c45fa40 100644 --- a/src/backend/src/modules/puterai/lib/Streaming.js +++ b/src/backend/src/modules/puterai/lib/Streaming.js @@ -2,9 +2,9 @@ * Assign the properties of the override object to the original object, * like Object.assign, except properties are ordered so override properties * are enumerated first. - * - * @param {*} original - * @param {*} override + * + * @param {*} original + * @param {*} override */ const objectAssignTop = (original, override) => { let o = { @@ -16,7 +16,7 @@ const objectAssignTop = (original, override) => { ...original, }; return o; -} +}; class AIChatConstructStream { constructor (chatStream, params) { @@ -33,14 +33,14 @@ class AIChatTextStream extends AIChatConstructStream { const json = JSON.stringify({ type: 'text', text, }); - this.chatStream.stream.write(json + '\n'); + this.chatStream.stream.write(`${json }\n`); } addReasoning (reasoning) { const json = JSON.stringify({ type: 'reasoning', reasoning, }); - this.chatStream.stream.write(json + '\n'); + this.chatStream.stream.write(`${json }\n`); } } @@ -60,11 +60,11 @@ class AIChatToolUseStream extends AIChatConstructStream { const str = JSON.stringify(objectAssignTop({ ...this.contentBlock, input: JSON.parse(this.buffer), - ...( ! this.contentBlock.text ? { text: "" } : {}), + ...( !this.contentBlock.text ? { text: '' } : {}), }, { type: 'tool_use', })); - this.chatStream.stream.write(str + '\n'); + this.chatStream.stream.write(`${str }\n`); } } @@ -95,5 +95,5 @@ class AIChatStream { } module.exports = class Streaming { - static AIChatStream = AIChatStream; -} + static AIChatStream = AIChatStream; +}; diff --git a/src/backend/src/modules/puterai/lib/messages.test.js b/src/backend/src/modules/puterai/lib/messages.test.js index 3a4c7b4e3e..0cee56ca8a 100644 --- a/src/backend/src/modules/puterai/lib/messages.test.js +++ b/src/backend/src/modules/puterai/lib/messages.test.js @@ -14,10 +14,10 @@ describe('Messages', () => { { type: 'text', text: 'Hello, world!', - } - ] - } - } + }, + ], + }, + }, ]; for ( const tc of cases ) { it(`should normalize ${tc.name}`, () => { @@ -40,8 +40,8 @@ describe('Messages', () => { { type: 'text', text: 'Hello, world!', - } - ] + }, + ], }], output: 'Hello, world!', }, @@ -54,15 +54,15 @@ describe('Messages', () => { { type: 'text', text: 'Second Part', - } - ] + }, + ], }, { content: 'Third Part', - } + }, ], output: 'First Part Second Part Third Part', - } + }, ]; for ( const tc of cases ) { it(`should extract text from ${tc.name}`, () => { @@ -84,9 +84,9 @@ describe('Messages', () => { function: { name: 'tool-1-function', arguments: {}, - } - } - ] + }, + }, + ], }, output: { role: 'assistant', @@ -96,10 +96,10 @@ describe('Messages', () => { id: 'tool-1', name: 'tool-1-function', input: {}, - } - ] - } - } + }, + ], + }, + }, ]; for ( const tc of cases ) { it(`should normalize ${tc.name}`, () => { @@ -119,9 +119,9 @@ describe('Messages', () => { type: 'tool_use', id: 'tool-1', name: 'tool-1-function', - input: "{}", - } - ] + input: '{}', + }, + ], }, output: { role: 'assistant', @@ -130,11 +130,11 @@ describe('Messages', () => { type: 'tool_use', id: 'tool-1', name: 'tool-1-function', - input: "{}", - } - ] - } - } + input: '{}', + }, + ], + }, + }, ]; for ( const tc of cases ) { it(`should normalize ${tc.name}`, () => { @@ -155,8 +155,8 @@ describe('Messages', () => { id: 'tool-1', name: 'tool-1-function', input: {}, - } - ] + }, + ], }], output: [{ role: 'assistant', @@ -168,11 +168,11 @@ describe('Messages', () => { function: { name: 'tool-1-function', arguments: '{}', - } - } - ] - }] - } + }, + }, + ], + }], + }, ]; for ( const tc of cases ) { it(`should normalize ${tc.name}`, async () => { diff --git a/src/backend/src/modules/puterai/samples/claude-1.js b/src/backend/src/modules/puterai/samples/claude-1.js index 705892b7bb..675d577208 100644 --- a/src/backend/src/modules/puterai/samples/claude-1.js +++ b/src/backend/src/modules/puterai/samples/claude-1.js @@ -13,53 +13,53 @@ module.exports = [ input_tokens: 82, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, - output_tokens: 1 - } - } + output_tokens: 1, + }, + }, }, { type: 'content_block_start', index: 0, - content_block: { type: 'text', text: '' } + content_block: { type: 'text', text: '' }, }, { type: 'content_block_delta', index: 0, - delta: { type: 'text_delta', text: 'Some' } + delta: { type: 'text_delta', text: 'Some' }, }, { type: 'content_block_delta', index: 0, - delta: { type: 'text_delta', text: ' species of fish, like the electric' } + delta: { type: 'text_delta', text: ' species of fish, like the electric' }, }, { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', - text: ' eel, can generate powerful electrical' - } + text: ' eel, can generate powerful electrical', + }, }, { type: 'content_block_delta', index: 0, - delta: { type: 'text_delta', text: ' charges of up to 860 ' } + delta: { type: 'text_delta', text: ' charges of up to 860 ' }, }, { type: 'content_block_delta', index: 0, - delta: { type: 'text_delta', text: 'volts to stun prey an' } + delta: { type: 'text_delta', text: 'volts to stun prey an' }, }, { type: 'content_block_delta', index: 0, - delta: { type: 'text_delta', text: 'd defend themselves.' } + delta: { type: 'text_delta', text: 'd defend themselves.' }, }, { type: 'content_block_stop', index: 0 }, { type: 'message_delta', delta: { stop_reason: 'end_turn', stop_sequence: null }, - usage: { output_tokens: 35 } + usage: { output_tokens: 35 }, }, { type: 'message_stop' }, -] \ No newline at end of file +]; \ No newline at end of file diff --git a/src/backend/src/modules/puterai/samples/claude-tools-1.js b/src/backend/src/modules/puterai/samples/claude-tools-1.js index 41a5495da9..c1ff69da06 100644 --- a/src/backend/src/modules/puterai/samples/claude-tools-1.js +++ b/src/backend/src/modules/puterai/samples/claude-tools-1.js @@ -13,27 +13,27 @@ module.exports = [ input_tokens: 458, cache_creation_input_tokens: 0, cache_read_input_tokens: 0, - output_tokens: 1 - } - } + output_tokens: 1, + }, + }, }, { type: 'content_block_start', index: 0, - content_block: { type: 'text', text: '' } + content_block: { type: 'text', text: '' }, }, { type: 'content_block_delta', index: 0, - delta: { type: 'text_delta', text: 'I' } + delta: { type: 'text_delta', text: 'I' }, }, { type: 'content_block_delta', index: 0, delta: { type: 'text_delta', - text: "'ll check the weather in Vancouver for you." - } + text: "'ll check the weather in Vancouver for you.", + }, }, { type: 'content_block_stop', index: 0 }, { @@ -43,34 +43,34 @@ module.exports = [ type: 'tool_use', id: 'toolu_01E12jeyCenTtntPBk1j7rgc', name: 'get_weather', - input: {} - } + input: {}, + }, }, { type: 'content_block_delta', index: 1, - delta: { type: 'input_json_delta', partial_json: '' } + delta: { type: 'input_json_delta', partial_json: '' }, }, { type: 'content_block_delta', index: 1, - delta: { type: 'input_json_delta', partial_json: '{"location"' } + delta: { type: 'input_json_delta', partial_json: '{"location"' }, }, { type: 'content_block_delta', index: 1, - delta: { type: 'input_json_delta', partial_json: ': "Van' } + delta: { type: 'input_json_delta', partial_json: ': "Van' }, }, { type: 'content_block_delta', index: 1, - delta: { type: 'input_json_delta', partial_json: 'couver"}' } + delta: { type: 'input_json_delta', partial_json: 'couver"}' }, }, { type: 'content_block_stop', index: 1 }, { type: 'message_delta', delta: { stop_reason: 'tool_use', stop_sequence: null }, - usage: { output_tokens: 64 } + usage: { output_tokens: 64 }, }, { type: 'message_stop' }, -] +]; diff --git a/src/backend/src/modules/puterai/samples/openai-1.js b/src/backend/src/modules/puterai/samples/openai-1.js index af606ed72b..3dab0dd971 100644 --- a/src/backend/src/modules/puterai/samples/openai-1.js +++ b/src/backend/src/modules/puterai/samples/openai-1.js @@ -10,20 +10,20 @@ module.exports = [ { index: 0, delta: { - role: "assistant", - content: "", - refusal: null + role: 'assistant', + content: '', + refusal: null, }, logprobs: null, - finish_reason: null - } + finish_reason: null, + }, ], - usage: null + usage: null, }, ...[ - `Fish`, ` are`, ` diverse`, ` aquatic`, ` creatures`, ` that`, ` play`, - ` a`, ` crucial`, ` role`, ` in`, ` marine`, ` ecosystems`, ` and`, - ` human`, ` diets`, `.` + 'Fish', ' are', ' diverse', ' aquatic', ' creatures', ' that', ' play', + ' a', ' crucial', ' role', ' in', ' marine', ' ecosystems', ' and', + ' human', ' diets', '.', ].map(str => ({ id: 'chatcmpl-AvspmQTvFBBjKsFhHYhyiphFmKMY8', object: 'chat.completion.chunk', @@ -32,15 +32,15 @@ module.exports = [ service_tier: 'default', system_fingerprint: 'fp_bd83329f63', choices: [ - { - index: 0, - delta: { - content: str + { + index: 0, + delta: { + content: str, + }, + logprobs: null, + finish_reason: null, }, - logprobs: null, - finish_reason: null - } ], - usage: null + usage: null, })), ]; diff --git a/src/backend/src/modules/puterai/samples/openai-tools-1.js b/src/backend/src/modules/puterai/samples/openai-tools-1.js index 3c75cc5dc3..728473029f 100644 --- a/src/backend/src/modules/puterai/samples/openai-tools-1.js +++ b/src/backend/src/modules/puterai/samples/openai-tools-1.js @@ -10,31 +10,31 @@ module.exports = [ { index: 0, delta: { - role: "assistant", + role: 'assistant', content: null, tool_calls: [ { index: 0, - id: "call_ULl8cRKFQbYeJSIZ3giLAg6r", - type: "function", + id: 'call_ULl8cRKFQbYeJSIZ3giLAg6r', + type: 'function', function: { - name: "get_weather", - arguments: "" - } - } + name: 'get_weather', + arguments: '', + }, + }, ], - refusal: null + refusal: null, }, logprobs: null, - finish_reason: null - } + finish_reason: null, + }, ], - usage: null + usage: null, }, ...[ - `{"`, `location`, `":"`, - `V`, `ancouver`, - `"}` + '{"', 'location', '":"', + 'V', 'ancouver', + '"}', ].map(str => ({ id: 'chatcmpl-Avqr6AwmQoEFLXuwf1llkKknIR4Ry', object: 'chat.completion.chunk', @@ -51,15 +51,15 @@ module.exports = [ index: 0, function: { arguments: str, - } - } - ] + }, + }, + ], }, logprobs: null, - finish_reason: null - } + finish_reason: null, + }, ], - usage: null + usage: null, })), { id: 'chatcmpl-Avqr6AwmQoEFLXuwf1llkKknIR4Ry', @@ -73,10 +73,10 @@ module.exports = [ index: 0, delta: {}, logprobs: null, - finish_reason: 'tool_calls' - } + finish_reason: 'tool_calls', + }, ], - usage: null + usage: null, }, { id: 'chatcmpl-Avqr6AwmQoEFLXuwf1llkKknIR4Ry', @@ -95,8 +95,8 @@ module.exports = [ reasoning_tokens: 0, audio_tokens: 0, accepted_prediction_tokens: 0, - rejected_prediction_tokens: 0 - } - } - } + rejected_prediction_tokens: 0, + }, + }, + }, ]; diff --git a/src/backend/src/modules/puterfs/MountpointService.js b/src/backend/src/modules/puterfs/MountpointService.js index 89384d2e3a..e7a95ba959 100644 --- a/src/backend/src/modules/puterfs/MountpointService.js +++ b/src/backend/src/modules/puterfs/MountpointService.js @@ -17,8 +17,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { RootNodeSelector, NodeUIDSelector, NodeChildSelector, NodePathSelector, try_infer_attributes } = require("../../filesystem/node/selectors"); -const BaseService = require("../../services/BaseService"); +const { RootNodeSelector, NodeUIDSelector, NodeChildSelector, NodePathSelector, try_infer_attributes } = require('../../filesystem/node/selectors'); +const BaseService = require('../../services/BaseService'); /** * This will eventually be a service which manages the storage @@ -43,11 +43,11 @@ class MountpointService extends BaseService { #mounters = {}; #mountpoints = {}; - register_mounter(name, mounter) { + register_mounter (name, mounter) { this.#mounters[name] = mounter; } - async ['__on_boot.consolidation']() { + async ['__on_boot.consolidation'] () { // Emit event for registering filesystem types const svc_event = this.services.get('event'); const event = {}; @@ -55,7 +55,7 @@ class MountpointService extends BaseService { this.#mounters[name] = filesystemType; }; await svc_event.emit('create.filesystem-types', event); - + // Determine mountpoints configuration const mountpoints = this.config.mountpoints ?? { '/': { @@ -85,7 +85,7 @@ class MountpointService extends BaseService { }); } - async get_provider(selector) { + async get_provider (selector) { // If there is only one provider, we don't need to do any of this, // and that's a big deal because the current implementation requires // fetching a filesystem entry before we even have operation-level @@ -145,7 +145,7 @@ class MountpointService extends BaseService { } // Temporary solution - we'll develop this incrementally - set_storage(provider, storage) { + set_storage (provider, storage) { this.#storage[provider] = storage; } @@ -153,7 +153,7 @@ class MountpointService extends BaseService { * Gets the current storage backend instance * @returns {Object} The storage backend instance */ - get_storage(provider) { + get_storage (provider) { const storage = this.#storage[provider]; if ( ! storage ) { throw new Error(`MountpointService.get_storage: storage for provider "${provider}" not found`); diff --git a/src/backend/src/modules/puterfs/ResourceService.js b/src/backend/src/modules/puterfs/ResourceService.js index 307ef352a0..45c6ddd075 100644 --- a/src/backend/src/modules/puterfs/ResourceService.js +++ b/src/backend/src/modules/puterfs/ResourceService.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); const { NodePathSelector, NodeUIDSelector, NodeInternalIDSelector, NodeChildSelector, -} = require("../../filesystem/node/selectors"); +} = require('../../filesystem/node/selectors'); const RESOURCE_STATUS_PENDING_CREATE = {}; const RESOURCE_STATUS_PENDING_UPDATE = {}; @@ -62,7 +62,7 @@ class ResourceService extends BaseService { }; }); entry.onFree = entry.freePromise.then.bind(entry.freePromise); - this.log.debug(`registering resource`, { uid: entry.uid }); + this.log.debug('registering resource', { uid: entry.uid }); this.uidToEntry[entry.uid] = entry; if ( entry.path ) { this.uidToPath[entry.uid] = entry.path; @@ -72,7 +72,7 @@ class ResourceService extends BaseService { } free (uid) { - this.log.debug(`freeing`, { uid }); + this.log.debug('freeing', { uid }); const entry = this.uidToEntry[uid]; if ( ! entry ) return; delete this.uidToEntry[uid]; @@ -86,7 +86,7 @@ class ResourceService extends BaseService { async waitForResourceByPath (path) { const entry = this.pathToEntry[path]; - if (!entry) { + if ( ! entry ) { return; } await entry.freePromise; @@ -94,7 +94,7 @@ class ResourceService extends BaseService { async waitForResourceByUID (uid) { const entry = this.uidToEntry[uid]; - if (!entry) { + if ( ! entry ) { return; } await entry.freePromise; @@ -105,13 +105,13 @@ class ResourceService extends BaseService { await this.waitForResourceByPath(selector.value); } else - if ( selector instanceof NodeUIDSelector ) { - await this.waitForResourceByUID(selector.value); - } - else - if ( selector instanceof NodeInternalIDSelector ) { - // Can't wait intelligently for this - } + if ( selector instanceof NodeUIDSelector ) { + await this.waitForResourceByUID(selector.value); + } + else + if ( selector instanceof NodeInternalIDSelector ) { + // Can't wait intelligently for this + } if ( selector instanceof NodeChildSelector ) { await this.waitForResource(selector.parent); } diff --git a/src/backend/src/modules/puterfs/SizeService.js b/src/backend/src/modules/puterfs/SizeService.js index 02cdb113db..650266f696 100644 --- a/src/backend/src/modules/puterfs/SizeService.js +++ b/src/backend/src/modules/puterfs/SizeService.js @@ -16,12 +16,12 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { get_dir_size, id2path, get_user, invalidate_cached_user_by_id } = require("../../helpers"); -const BaseService = require("../../services/BaseService"); +const { get_dir_size, id2path, get_user, invalidate_cached_user_by_id } = require('../../helpers'); +const BaseService = require('../../services/BaseService'); -const { DB_WRITE } = require("../../services/database/consts"); -const { Context } = require("../../util/context"); -const { nou } = require("../../util/langutil"); +const { DB_WRITE } = require('../../services/database/consts'); +const { Context } = require('../../util/context'); +const { nou } = require('../../util/langutil'); // TODO: expose to a utility library class UserParameter { @@ -38,7 +38,7 @@ class SizeService extends BaseService { _construct () { this.usages = {}; } - + _init () { this.db = this.services.get('database').get(DB_WRITE, 'filesystem'); @@ -54,7 +54,7 @@ class SizeService extends BaseService { const user = await UserParameter.adapt(args[0]); const usage = await this.get_usage(user.id); log.log(`usage: ${usage} bytes`); - } + }, }, { id: 'get-capacity', @@ -63,7 +63,7 @@ class SizeService extends BaseService { const user = await UserParameter.adapt(args[0]); const capacity = await this.get_storage_capacity(user); log.log(`capacity: ${capacity} bytes`); - } + }, }, { id: 'get-cache-size', @@ -71,9 +71,9 @@ class SizeService extends BaseService { handler: async (args, log) => { const size = Object.keys(this.usages).length; log.log(`cache size: ${size}`); - } + }, }, - ]) + ]); } async get_usage (user_id) { @@ -81,11 +81,9 @@ class SizeService extends BaseService { // return this.usages[user_id]; // } - const fsentry = await this.db.read( - "SELECT SUM(size) AS total FROM `fsentries` WHERE `user_id` = ? LIMIT 1", - [user_id] - ); - if(!fsentry[0] || !fsentry[0].total) { + const fsentry = await this.db.read('SELECT SUM(size) AS total FROM `fsentries` WHERE `user_id` = ? LIMIT 1', + [user_id]); + if ( !fsentry[0] || !fsentry[0].total ) { this.usages[user_id] = 0; } else { this.usages[user_id] = parseInt(fsentry[0].total); @@ -102,7 +100,7 @@ class SizeService extends BaseService { // TODO: remove fs arg and update all calls async add_node_size (fs, node, user, factor = 1) { const { - fsEntryService + fsEntryService, } = Context.get('services').values; let sz; @@ -112,7 +110,7 @@ class SizeService extends BaseService { } else { // very unlikely, but a warning is better than a throw right now // TODO: remove this once we're sure this is never hit - this.log.warn('add_node_size: node has no uuid :(', node) + this.log.warn('add_node_size: node has no uuid :(', node); sz = await get_dir_size(await id2path(node.mysql_id), user); } } else { @@ -126,7 +124,7 @@ class SizeService extends BaseService { if ( ! this.global_config.is_storage_limited ) { return this.global_config.available_device_storage; } - + if ( nou(user.free_storage) ) { return this.global_config.storage_capacity; } @@ -166,26 +164,22 @@ class SizeService extends BaseService { const values = fields_.map(f => entry[f]); try { - await this.db.write( - `INSERT INTO storage_audit (${fields}) VALUES (${placeholders})`, - values, - ); + await this.db.write(`INSERT INTO storage_audit (${fields}) VALUES (${placeholders})`, + values); } catch (e) { this.errors.report('size-service.audit-add-storage', { source: e, trace: true, alarm: true, - }) + }); } } // Storage increase { try { - const res = await this.db.write( - "UPDATE `user` SET `free_storage` = ? WHERE `id` = ? LIMIT 1", - [capacity + amount_in_bytes, user.id] - ); + const res = await this.db.write('UPDATE `user` SET `free_storage` = ? WHERE `id` = ? LIMIT 1', + [capacity + amount_in_bytes, user.id]); if ( ! res.anyRowsAffected ) { throw new Error(`add_storage: failed to update user ${user.id}`); } @@ -194,7 +188,7 @@ class SizeService extends BaseService { source: e, trace: true, alarm: true, - }) + }); } invalidate_cached_user_by_id(user.id); } diff --git a/src/backend/src/modules/puterfs/customfs/MemoryFSProvider.js b/src/backend/src/modules/puterfs/customfs/MemoryFSProvider.js index cb35b6f0dd..47e1be8022 100644 --- a/src/backend/src/modules/puterfs/customfs/MemoryFSProvider.js +++ b/src/backend/src/modules/puterfs/customfs/MemoryFSProvider.js @@ -41,7 +41,7 @@ class MemoryFile { * @param {Buffer|null} param.content - The content of the file, `null` if the file is a directory. * @param {string|null} [param.parent_uid] - UID of parent directory; null for root. */ - constructor({ path, is_dir, content, parent_uid = null }) { + constructor ({ path, is_dir, content, parent_uid = null }) { this.uuid = uuidv4(); this.is_public = true; @@ -73,7 +73,7 @@ class MemoryFile { } class MemoryFSProvider { - constructor(mountpoint) { + constructor (mountpoint) { this.mountpoint = mountpoint; // key: relative path from the mountpoint, always starts with `/` @@ -101,7 +101,7 @@ class MemoryFSProvider { * * @returns {Set} - Set of capabilities supported by this provider. */ - get_capabilities() { + get_capabilities () { return new Set([ fsCapabilities.READDIR_UUID_MODE, fsCapabilities.UUID, @@ -117,17 +117,17 @@ class MemoryFSProvider { * @param {string} path - The path to normalize. * @returns {string} - The normalized path, always starts with `/`. */ - _inner_path(path) { - if (!path) { + _inner_path (path) { + if ( ! path ) { return '/'; } - if (path.startsWith(this.mountpoint)) { + if ( path.startsWith(this.mountpoint) ) { path = path.slice(this.mountpoint.length); } - if (!path.startsWith('/')) { - path = '/' + path; + if ( ! path.startsWith('/') ) { + path = `/${ path}`; } return path; @@ -138,57 +138,55 @@ class MemoryFSProvider { * * @returns {Promise} */ - _integrity_check() { - if (config.env !== 'dev') { + _integrity_check () { + if ( config.env !== 'dev' ) { // only check in debug mode since it's expensive return; } // check the 2 maps are consistent - if (this.entriesByPath.size !== this.entriesByUUID.size) { + if ( this.entriesByPath.size !== this.entriesByUUID.size ) { throw new Error('Path map and UUID map have different sizes'); } - for (const [inner_path, uuid] of this.entriesByPath) { + for ( const [inner_path, uuid] of this.entriesByPath ) { const entry = this.entriesByUUID.get(uuid); // entry should exist - if (!entry) { + if ( ! entry ) { throw new Error(`Entry ${uuid} does not exist`); } // path should match - if (this._inner_path(entry.path) !== inner_path) { + if ( this._inner_path(entry.path) !== inner_path ) { throw new Error(`Path ${inner_path} does not match entry ${uuid}`); } // uuid should match - if (entry.uuid !== uuid) { + if ( entry.uuid !== uuid ) { throw new Error(`UUID ${uuid} does not match entry ${entry.uuid}`); } // parent should exist - if (entry.parent_uid) { + if ( entry.parent_uid ) { const parent_entry = this.entriesByUUID.get(entry.parent_uid); - if (!parent_entry) { + if ( ! parent_entry ) { throw new Error(`Parent ${entry.parent_uid} does not exist`); } } // parent's path should be a prefix of the entry's path - if (entry.parent_uid) { + if ( entry.parent_uid ) { const parent_entry = this.entriesByUUID.get(entry.parent_uid); - if (!entry.path.startsWith(parent_entry.path)) { - throw new Error( - `Parent ${entry.parent_uid} path ${parent_entry.path} is not a prefix of entry ${entry.path}`, - ); + if ( ! entry.path.startsWith(parent_entry.path) ) { + throw new Error(`Parent ${entry.parent_uid} path ${parent_entry.path} is not a prefix of entry ${entry.path}`); } } // parent should be a directory - if (entry.parent_uid) { + if ( entry.parent_uid ) { const parent_entry = this.entriesByUUID.get(entry.parent_uid); - if (!parent_entry.is_dir) { + if ( ! parent_entry.is_dir ) { throw new Error(`Parent ${entry.parent_uid} is not a directory`); } } @@ -202,13 +200,13 @@ class MemoryFSProvider { * @param {NodePathSelector | NodeUIDSelector | NodeChildSelector | RootNodeSelector | NodeRawEntrySelector} param.selector - The selector used for checking. * @returns {Promise} - True if the node exists, false otherwise. */ - async quick_check({ selector }) { - if (selector instanceof NodePathSelector) { + async quick_check ({ selector }) { + if ( selector instanceof NodePathSelector ) { const inner_path = this._inner_path(selector.value); return this.entriesByPath.has(inner_path); } - if (selector instanceof NodeUIDSelector) { + if ( selector instanceof NodeUIDSelector ) { return this.entriesByUUID.has(selector.value); } @@ -219,7 +217,7 @@ class MemoryFSProvider { /** * Performs a stat operation using the given selector. - * + * * NB: Some returned fields currently contain placeholder values. And the * `path` of the absolute path from the root. * @@ -227,20 +225,20 @@ class MemoryFSProvider { * @param {NodePathSelector | NodeUIDSelector | NodeChildSelector | RootNodeSelector | NodeRawEntrySelector} param.selector - The selector to stat. * @returns {Promise} - The result of the stat operation, or `null` if the node doesn't exist. */ - async stat({ selector }) { + async stat ({ selector }) { try_infer_attributes(selector); let entry_uuid = null; - if (selector instanceof NodePathSelector) { + if ( selector instanceof NodePathSelector ) { // stat by path const inner_path = this._inner_path(selector.value); entry_uuid = this.entriesByPath.get(inner_path); - } else if (selector instanceof NodeUIDSelector) { + } else if ( selector instanceof NodeUIDSelector ) { // stat by uid entry_uuid = selector.value; - } else if (selector instanceof NodeChildSelector) { - if (selector.path) { + } else if ( selector instanceof NodeChildSelector ) { + if ( selector.path ) { // Shouldn't care about about parent when the "path" is present // since it might have different provider. return await this.stat({ @@ -251,7 +249,7 @@ class MemoryFSProvider { const parent_entry = await this.stat({ selector: selector.parent, }); - if (parent_entry) { + if ( parent_entry ) { const full_path = _path.join(parent_entry.path, selector.name); return await this.stat({ selector: new NodePathSelector(full_path), @@ -264,7 +262,7 @@ class MemoryFSProvider { } const entry = this.entriesByUUID.get(entry_uuid); - if (!entry) { + if ( ! entry ) { return null; } @@ -283,10 +281,10 @@ class MemoryFSProvider { * @param {FSNodeContext} param.node - The directory node to read. * @returns {Promise} - Array of child UUIDs. */ - async readdir({ context, node }) { + async readdir ({ context, node }) { // prerequistes: get required path via stat const entry = await this.stat({ selector: node.selector }); - if (!entry) { + if ( ! entry ) { throw APIError.create('invalid_node'); } @@ -294,13 +292,13 @@ class MemoryFSProvider { const child_uuids = []; // Find all entries that are direct children of this directory - for (const [path, uuid] of this.entriesByPath) { - if (path === inner_path) { + for ( const [path, uuid] of this.entriesByPath ) { + if ( path === inner_path ) { continue; // Skip the directory itself } const dirname = _path.dirname(path); - if (dirname === inner_path) { + if ( dirname === inner_path ) { child_uuids.push(uuid); } } @@ -317,10 +315,10 @@ class MemoryFSProvider { * @param {string} param.name - The name of the new directory. * @returns {Promise} - The new directory node. */ - async mkdir({ context, parent, name }) { + async mkdir ({ context, parent, name }) { // prerequistes: get required path via stat const parent_entry = await this.stat({ selector: parent.selector }); - if (!parent_entry) { + if ( ! parent_entry ) { throw APIError.create('invalid_node'); } @@ -328,7 +326,7 @@ class MemoryFSProvider { const inner_path = this._inner_path(full_path); let entry = null; - if (this.entriesByPath.has(inner_path)) { + if ( this.entriesByPath.has(inner_path) ) { throw APIError.create('item_with_same_name_exists', null, { entry_name: full_path, }); @@ -362,40 +360,40 @@ class MemoryFSProvider { * @param {Object} param.options: The options for the operation. * @returns {Promise} */ - async rmdir({ context, node, options = {} }) { + async rmdir ({ context, node, options = {} }) { this._integrity_check(); // prerequistes: get required path via stat const entry = await this.stat({ selector: node.selector }); - if (!entry) { + if ( ! entry ) { throw APIError.create('invalid_node'); } const inner_path = this._inner_path(entry.path); // for mode: non-recursive - if (!options.recursive) { + if ( ! options.recursive ) { const children = await this.readdir({ context, node }); - if (children.length > 0) { + if ( children.length > 0 ) { throw APIError.create('not_empty'); } } // remove all descendants - for (const [other_inner_path, other_entry_uuid] of this.entriesByPath) { - if (other_entry_uuid === entry.uuid) { + for ( const [other_inner_path, other_entry_uuid] of this.entriesByPath ) { + if ( other_entry_uuid === entry.uuid ) { // skip the directory itself continue; } - if (other_inner_path.startsWith(inner_path)) { + if ( other_inner_path.startsWith(inner_path) ) { this.entriesByPath.delete(other_inner_path); this.entriesByUUID.delete(other_entry_uuid); } } // for mode: non-descendants-only - if (!options.descendants_only) { + if ( ! options.descendants_only ) { // remove the directory itself this.entriesByPath.delete(inner_path); this.entriesByUUID.delete(entry.uuid); @@ -412,10 +410,10 @@ class MemoryFSProvider { * @param {FSNodeContext} param.node: The file to remove. * @returns {Promise} */ - async unlink({ context, node }) { + async unlink ({ context, node }) { // prerequistes: get required path via stat const entry = await this.stat({ selector: node.selector }); - if (!entry) { + if ( ! entry ) { throw APIError.create('invalid_node'); } @@ -435,10 +433,10 @@ class MemoryFSProvider { * @param {Object} param.metadata: The metadata of the file. * @returns {Promise} */ - async move({ context, node, new_parent, new_name, metadata }) { + async move ({ context, node, new_parent, new_name, metadata }) { // prerequistes: get required path via stat const new_parent_entry = await this.stat({ selector: new_parent.selector }); - if (!new_parent_entry) { + if ( ! new_parent_entry ) { throw APIError.create('invalid_node'); } @@ -476,16 +474,16 @@ class MemoryFSProvider { * @param {string} param.target_name - The name for the copied item. * @returns {Promise} - The copied node. */ - async copy_tree({ context, source, parent, target_name }) { + async copy_tree ({ context, source, parent, target_name }) { const fs = context.get('services').get('filesystem'); - if (source.entry.is_dir) { + if ( source.entry.is_dir ) { // Create the directory const new_dir = await this.mkdir({ context, parent, name: target_name }); // Copy all children const children = await this.readdir({ context, node: source }); - for (const child_uuid of children) { + for ( const child_uuid of children ) { const child_node = await fs.node(new NodeUIDSelector(child_uuid)); await child_node.fetchEntry(); const child_name = child_node.entry.name; @@ -522,17 +520,17 @@ class MemoryFSProvider { * @param {Object} param.file: The file to write. * @returns {Promise} */ - async write_new({ context, parent, name, file }) { + async write_new ({ context, parent, name, file }) { // prerequistes: get required path via stat const parent_entry = await this.stat({ selector: parent.selector }); - if (!parent_entry) { + if ( ! parent_entry ) { throw APIError.create('invalid_node'); } const full_path = _path.join(parent_entry.path, name); const inner_path = this._inner_path(full_path); let entry = null; - if (this.entriesByPath.has(inner_path)) { + if ( this.entriesByPath.has(inner_path) ) { throw APIError.create('item_with_same_name_exists', null, { entry_name: full_path, }); @@ -566,20 +564,20 @@ class MemoryFSProvider { * @param {Object} param.file: The file to write. * @returns {Promise} */ - async write_overwrite({ context, node, file }) { + async write_overwrite ({ context, node, file }) { const entry = await this.stat({ selector: node.selector }); - if (!entry) { + if ( ! entry ) { throw APIError.create('invalid_node'); } const inner_path = this._inner_path(entry.path); this.entriesByPath.set(inner_path, entry.uuid); let original_entry = this.entriesByUUID.get(entry.uuid); - if (!original_entry) { + if ( ! original_entry ) { throw new Error(`File ${entry.path} does not exist`); } else { - if (original_entry.is_dir) { - throw new Error(`Cannot overwrite a directory`); + if ( original_entry.is_dir ) { + throw new Error('Cannot overwrite a directory'); } original_entry.content = file.stream.read(); @@ -597,7 +595,7 @@ class MemoryFSProvider { return node; } - async read({ + async read ({ context, node, }) { diff --git a/src/backend/src/modules/puterfs/customfs/MemoryFSService.js b/src/backend/src/modules/puterfs/customfs/MemoryFSService.js index 99397ecfbd..198bfba1c9 100644 --- a/src/backend/src/modules/puterfs/customfs/MemoryFSService.js +++ b/src/backend/src/modules/puterfs/customfs/MemoryFSService.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../../services/BaseService"); -const { MemoryFSProvider } = require("./MemoryFSProvider"); +const BaseService = require('../../../services/BaseService'); +const { MemoryFSProvider } = require('./MemoryFSProvider'); class MemoryFSService extends BaseService { async _init () { @@ -31,9 +31,9 @@ class MemoryFSService extends BaseService { async mount ({ path, options }) { const provider = new MemoryFSProvider(path); return provider; - } - } - } + }, + }, + }; } module.exports = { diff --git a/src/backend/src/modules/selfhosted/ComplainAboutVersionsService.js b/src/backend/src/modules/selfhosted/ComplainAboutVersionsService.js index ed0011a214..f11185bf50 100644 --- a/src/backend/src/modules/selfhosted/ComplainAboutVersionsService.js +++ b/src/backend/src/modules/selfhosted/ComplainAboutVersionsService.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ const BaseService = require('../../services/BaseService'); -const { surrounding_box } = require("../../fun/dev-console-ui-utils"); +const { surrounding_box } = require('../../fun/dev-console-ui-utils'); class ComplainAboutVersionsService extends BaseService { static DESCRIPTION = ` @@ -33,20 +33,16 @@ class ComplainAboutVersionsService extends BaseService { static MODULES = { axios: require('axios'), - } + }; async _init () { const eol_data = await this.get_eol_data_(); const [major] = process.versions.node.split('.'); - const current_version_data = eol_data.find( - ({ cycle }) => cycle === major - ); + const current_version_data = eol_data.find(({ cycle }) => cycle === major); if ( ! current_version_data ) { - this.log.warn( - `failed to check ${major} in the EOL database` - ); + this.log.warn(`failed to check ${major} in the EOL database`); return; } @@ -61,7 +57,7 @@ class ComplainAboutVersionsService extends BaseService { let timeago = (() => { let years = cur_date_obj.getFullYear() - eol_date.getFullYear(); let months = cur_date_obj.getMonth() - eol_date.getMonth(); - + let str = ''; while ( years > 0 ) { years -= 1; @@ -70,7 +66,7 @@ class ComplainAboutVersionsService extends BaseService { if ( months > 0 ) { str += `at least ${months} month${months > 1 ? 's' : ''}`; } else { - str += `a few days`; + str += 'a few days'; } return str; })(); diff --git a/src/backend/src/modules/selfhosted/DefaultUserService.js b/src/backend/src/modules/selfhosted/DefaultUserService.js index a71da1695b..532d23cedc 100644 --- a/src/backend/src/modules/selfhosted/DefaultUserService.js +++ b/src/backend/src/modules/selfhosted/DefaultUserService.js @@ -16,18 +16,18 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { QuickMkdir } = require("../../filesystem/hl_operations/hl_mkdir"); -const { HLWrite } = require("../../filesystem/hl_operations/hl_write"); -const { NodePathSelector } = require("../../filesystem/node/selectors"); -const { surrounding_box } = require("../../fun/dev-console-ui-utils"); -const { get_user, invalidate_cached_user } = require("../../helpers"); -const { Context } = require("../../util/context"); +const { QuickMkdir } = require('../../filesystem/hl_operations/hl_mkdir'); +const { HLWrite } = require('../../filesystem/hl_operations/hl_write'); +const { NodePathSelector } = require('../../filesystem/node/selectors'); +const { surrounding_box } = require('../../fun/dev-console-ui-utils'); +const { get_user, invalidate_cached_user } = require('../../helpers'); +const { Context } = require('../../util/context'); const { asyncSafeSetInterval } = require('@heyputer/putility').libs.promise; -const { buffer_to_stream } = require("../../util/streamutil"); -const BaseService = require("../../services/BaseService"); -const { Actor, UserActorType } = require("../../services/auth/Actor"); -const { DB_WRITE } = require("../../services/database/consts"); -const { TEAL } = require("../../services/NullDevConsoleService"); +const { buffer_to_stream } = require('../../util/streamutil'); +const BaseService = require('../../services/BaseService'); +const { Actor, UserActorType } = require('../../services/auth/Actor'); +const { DB_WRITE } = require('../../services/database/consts'); +const { TEAL } = require('../../services/NullDevConsoleService'); const { quot } = require('@heyputer/putility').libs.string; const USERNAME = 'admin'; @@ -35,43 +35,43 @@ const USERNAME = 'admin'; const DEFAULT_FILES = { '.policy': { 'drivers.json': JSON.stringify({ - "temp": { - "kv": { - "rate-limit": { - "max": 1000, - "period": 30000 - } + 'temp': { + 'kv': { + 'rate-limit': { + 'max': 1000, + 'period': 30000, + }, }, - "es": { - "rate-limit": { - "max": 1000, - "period": 30000 - } + 'es': { + 'rate-limit': { + 'max': 1000, + 'period': 30000, + }, }, }, - "user": { - "kv": { - "rate-limit": { - "max": 3000, - "period": 30000 - } + 'user': { + 'kv': { + 'rate-limit': { + 'max': 3000, + 'period': 30000, + }, }, - "es": { - "rate-limit": { - "max": 3000, - "period": 30000 - } - } - } + 'es': { + 'rate-limit': { + 'max': 3000, + 'period': 30000, + }, + }, + }, }, undefined, ' '), - } + }, }; class DefaultUserService extends BaseService { static MODULES = { bcrypt: require('bcrypt'), uuidv4: require('uuid').v4, - } + }; async _init () { this._register_commands(this.services.get('commands')); } @@ -84,10 +84,8 @@ class DefaultUserService extends BaseService { const require = this.require; const tmp_password = await this.get_tmp_password_(user); const bcrypt = require('bcrypt'); - const is_default_password = await bcrypt.compare( - tmp_password, - user.password - ); + const is_default_password = await bcrypt.compare(tmp_password, + user.password); if ( ! is_default_password ) return; // console.log(`password for admin is: ${tmp_password}`); @@ -117,14 +115,13 @@ class DefaultUserService extends BaseService { }); realConsole.log('\n'); - // show console widget this.default_user_widget = ({ is_docker }) => { if ( is_docker ) { // In Docker we keep the output as simple as possible because // we're unable to determine the size of the terminal return [ - 'Password for `admin`: ' + tmp_password, + `Password for \`admin\`: ${ tmp_password}`, // TODO: possible bug // These blank lines are necessary for it to render and // I'm not entirely sure why anymore. @@ -132,10 +129,10 @@ class DefaultUserService extends BaseService { ]; } const lines = [ - `Your admin user has been created!`, + 'Your admin user has been created!', `\x1B[31;1musername:\x1B[0m ${USERNAME}`, `\x1B[32;1mpassword:\x1B[0m ${tmp_password}`, - `(change the password to remove this message)` + '(change the password to remove this message)', ]; surrounding_box('31;1', lines); return lines; @@ -150,10 +147,8 @@ class DefaultUserService extends BaseService { const user = await get_user({ username: USERNAME }); const require = this.require; const bcrypt = require('bcrypt'); - const is_default_password = await bcrypt.compare( - tmp_password, - user.password - ); + const is_default_password = await bcrypt.compare(tmp_password, + user.password); if ( ! is_default_password ) { const svc_devConsole = this.services.get('dev-console'); svc_devConsole.remove_widget(this.default_user_widget); @@ -164,43 +159,37 @@ class DefaultUserService extends BaseService { } async create_default_user_ () { const db = this.services.get('database').get(DB_WRITE, USERNAME); - await db.write( - ` + await db.write(` INSERT INTO user (uuid, username, free_storage) VALUES (?, ?, ?) `, - [ - this.modules.uuidv4(), - USERNAME, - 1024 * 1024 * 1024 * 10, // 10 GB - ], - ); + [ + this.modules.uuidv4(), + USERNAME, + 1024 * 1024 * 1024 * 10, // 10 GB + ]); const svc_group = this.services.get('group'); await svc_group.add_users({ uid: 'ca342a5e-b13d-4dee-9048-58b11a57cc55', // admin - users: [USERNAME] + users: [USERNAME], }); const user = await get_user({ username: USERNAME, cached: false }); const actor = Actor.adapt(user); const tmp_password = await this.get_tmp_password_(user); const bcrypt = require('bcrypt'); const password_hashed = await bcrypt.hash(tmp_password, 8); - await db.write( - `UPDATE user SET password = ? WHERE id = ?`, - [ - password_hashed, - user.id, - ], - ); + await db.write('UPDATE user SET password = ? WHERE id = ?', + [ + password_hashed, + user.id, + ]); user.password = password_hashed; const svc_user = this.services.get('user'); await svc_user.generate_default_fsentries({ user }); // generate default files for admin user const svc_fs = this.services.get('filesystem'); const make_tree_ = async ({ components, tree }) => { - const parent = await svc_fs.node( - new NodePathSelector('/'+components.join('/')), - ); + const parent = await svc_fs.node(new NodePathSelector(`/${components.join('/')}`)); for ( const k in tree ) { if ( typeof tree[k] === 'string' ) { const buffer = Buffer.from(tree[k], 'utf-8'); @@ -226,13 +215,13 @@ class DefaultUserService extends BaseService { tree: tree[k], }); } - + } }; await Context.get().sub({ user, actor }).arun(async () => { await make_tree_({ components: ['admin'], - tree: DEFAULT_FILES + tree: DEFAULT_FILES, }); }); invalidate_cached_user(user); @@ -258,7 +247,7 @@ class DefaultUserService extends BaseService { args: { key: 'tmp_password', value: tmp_password, - } + }, }); return tmp_password; }); @@ -278,15 +267,13 @@ class DefaultUserService extends BaseService { args: { key: 'tmp_password', value: tmp_password, - } + }, }); - await db.write( - `UPDATE user SET password = ? WHERE id = ?`, - [ - password_hashed, - user.id, - ], - ); + await db.write('UPDATE user SET password = ? WHERE id = ?', + [ + password_hashed, + user.id, + ]); return tmp_password; }); } @@ -299,8 +286,8 @@ class DefaultUserService extends BaseService { const user = await get_user({ username }); const tmp_pwd = await this.force_tmp_password_(user); ctx.log(`New password for ${quot(username)} is: ${tmp_pwd}`); - } - } + }, + }, ]); } } diff --git a/src/backend/src/modules/selfhosted/DevCreditService.js b/src/backend/src/modules/selfhosted/DevCreditService.js index 07b2865425..1375c99ece 100644 --- a/src/backend/src/modules/selfhosted/DevCreditService.js +++ b/src/backend/src/modules/selfhosted/DevCreditService.js @@ -1,4 +1,4 @@ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); /** * PermissiveCreditService listens to the event where DriverService asks @@ -10,14 +10,14 @@ const BaseService = require("../../services/BaseService"); class PermissiveCreditService extends BaseService { static MODULES = { uuidv4: require('uuid').v4, - } + }; _init () { // Maps usernames to simulated credit amounts // (used when config.simulated_credit is set) this.simulated_credit_ = {}; const svc_event = this.services.get('event'); - svc_event.on(`credit.check-available`, (_, event) => { + svc_event.on('credit.check-available', (_, event) => { const username = event.actor.type.user.username; event.available = this.get_user_credit_(username); @@ -26,11 +26,11 @@ class PermissiveCreditService extends BaseService { // Useful for testing with Polly // event.available = 9000; - + // Useful for testing judge0 // event.available = 50_000; // event.avaialble = 49_999; - + // Useful for testing ConvertAPI // event.available = 4_500_000; // event.available = 4_499_999; @@ -39,11 +39,10 @@ class PermissiveCreditService extends BaseService { // event.available = 150_000; // event.available = 149_999; }); - + svc_event.on('credit.record-cost', (_, event) => { const username = event.actor.type.user.username; - event.available = this.consume_user_credit_( - username, event.cost); + event.available = this.consume_user_credit_(username, event.cost); if ( ! this.config.simulated_credit ) return; // Update usage settings tab in UI @@ -57,13 +56,13 @@ class PermissiveCreditService extends BaseService { }, }); }); - + svc_event.on('usages.query', (_, event) => { const username = event.actor.type.user.username; if ( ! this.config.simulated_credit ) { event.usages.push({ id: 'dev-credit', - name: `Unlimited Credit`, + name: 'Unlimited Credit', used: 0, available: 1, }); @@ -89,7 +88,7 @@ class PermissiveCreditService extends BaseService { } consume_user_credit_ (username, amount) { if ( ! this.config.simulated_credit ) return; - + if ( ! this.simulated_credit_[username] ) { this.simulated_credit_[username] = this.config.simulated_credit; } diff --git a/src/backend/src/modules/selfhosted/DevWatcherService.js b/src/backend/src/modules/selfhosted/DevWatcherService.js index 81b97fd4dc..f5fcea8b91 100644 --- a/src/backend/src/modules/selfhosted/DevWatcherService.js +++ b/src/backend/src/modules/selfhosted/DevWatcherService.js @@ -16,8 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { webpack, web } = require("webpack"); -const BaseService = require("../../services/BaseService"); +const { webpack, web } = require('webpack'); +const BaseService = require('../../services/BaseService'); const path_ = require('node:path'); const fs = require('node:fs'); @@ -31,7 +31,7 @@ class ProxyLogger { stream.on('data', (chunk) => { buffer += chunk.toString(); let lineEndIndex = buffer.indexOf('\n'); - while (lineEndIndex !== -1) { + while ( lineEndIndex !== -1 ) { const line = buffer.substring(0, lineEndIndex); this.log(line); buffer = buffer.substring(lineEndIndex + 1); @@ -40,7 +40,7 @@ class ProxyLogger { }); stream.on('end', () => { - if (buffer.length) { + if ( buffer.length ) { this.log(buffer); } }); @@ -60,7 +60,7 @@ class DevWatcherService extends BaseService { async _init (args) { this.args = args; } - + // Oh geez we need to wait for the web server to initialize // so that `config.origin` has the actual port in it if the // port is set to `auto` - you have no idea how confusing @@ -71,12 +71,11 @@ class DevWatcherService extends BaseService { let { root, commands, webpack } = this.args; if ( ! webpack ) webpack = []; - + let promises = []; for ( const entry of commands ) { const { directory } = entry; - const fullpath = this.modules.path.join( - root, directory); + const fullpath = this.modules.path.join(root, directory); // promises.push(this.start_({ ...entry, fullpath })); promises.push(svc_process.start({ ...entry, fullpath })); } @@ -118,7 +117,7 @@ class DevWatcherService extends BaseService { const packageJSONObject = JSON.parse(fs.readFileSync(packageJSONPath)); moduleType = packageJSONObject?.type ?? 'module'; } - + return { configjsPath, moduleType, @@ -140,7 +139,7 @@ class DevWatcherService extends BaseService { configIsFor: 'webpack', // for error message possibleConfigNames, }); - + let oldEnv; if ( entry.env ) { @@ -153,7 +152,7 @@ class DevWatcherService extends BaseService { } catch (e) { // Config service not available yet, will use null } - + for ( const k in entry.env ) { const envValue = entry.env[k]; // If it's a function, call it with the config, otherwise use the value directly @@ -169,8 +168,8 @@ class DevWatcherService extends BaseService { // If config is not available yet, don't set the env var // This allows the webpack config to use its fallback values from config files // Only log if it's not a null/undefined access error (which is expected) - if ( !e.message.includes('Cannot read properties of null') && - !e.message.includes('Cannot read properties of undefined') ) { + if ( !e.message.includes('Cannot read properties of null') && + !e.message.includes('Cannot read properties of undefined') ) { this.log.warn(`Could not evaluate env function for ${k}: ${e.message}`); } } @@ -183,22 +182,22 @@ class DevWatcherService extends BaseService { let webpackConfig = moduleType === 'module' ? (await import(webpackConfigPath)).default : require(webpackConfigPath); - + // The webpack config can sometimes be a function if ( typeof webpackConfig === 'function' ) { webpackConfig = await webpackConfig(); } if ( oldEnv ) process.env = oldEnv; - + webpackConfig.context = webpackConfig.context ? path_.resolve(path_.join(this.args.root, entry.directory), webpackConfig.context) : path_.join(this.args.root, entry.directory); - + if ( entry.onConfig ) entry.onConfig(webpackConfig); const webpacker = webpack(webpackConfig); - + let errorAfterLastEnd = false; let firstEvent = true; webpacker.watch({}, (err, stats) => { @@ -207,7 +206,7 @@ class DevWatcherService extends BaseService { firstEvent = false; hideSuccess = true; } - if (err || stats.hasErrors()) { + if ( err || stats.hasErrors() ) { // Extract error information without serializing the entire stats object const errorInfo = { err: err ? err.message : null, diff --git a/src/backend/src/modules/selfhosted/MinLogService.js b/src/backend/src/modules/selfhosted/MinLogService.js index 0d5b7581e9..98ba6bf428 100644 --- a/src/backend/src/modules/selfhosted/MinLogService.js +++ b/src/backend/src/modules/selfhosted/MinLogService.js @@ -1,18 +1,18 @@ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class MinLogService extends BaseService { static DESCRIPTION = ` MinLogService hides any log messages which specify an area of concern. A developer can enable particular areas of concern through the console. - ` - + `; + _construct () { this.on = false; this.visible = new Set(); - + this.widget_ = null; } - + _init () { // On operating systems where low-level config (high customization) is // expected, we can turn off minlog by default. @@ -32,31 +32,31 @@ class MinLogService extends BaseService { return { skip: true }; } } - + return; }); - + this._register_commands(this.services.get('commands')); } - - add_dev_console_widget_() { + + add_dev_console_widget_ () { const svc_devConsole = this.services.get('dev-console', { optional: true }); if ( ! svc_devConsole ) return; - + this.widget_ = () => { if ( ! this.on ) return ['minlog is off']; const lines = [ - `\x1B[31;1mSome logs hidden! Type minlog:off to see all logs.\x1B[0m` + '\x1B[31;1mSome logs hidden! Type minlog:off to see all logs.\x1B[0m', ]; return lines; - } + }; svc_devConsole.add_widget(this.widget_); } - rm_dev_console_widget_() { + rm_dev_console_widget_ () { const svc_devConsole = this.services.get('dev-console', { optional: true }); if ( ! svc_devConsole ) return; - + const lines = this.widget_(); this.log.info(lines[0]); @@ -70,14 +70,14 @@ class MinLogService extends BaseService { id: 'on', handler: async (args, log) => { this.on = true; - } + }, }, { id: 'off', handler: async (args, log) => { this.rm_dev_console_widget_(); this.on = false; - } + }, }, { id: 'show', @@ -85,7 +85,7 @@ class MinLogService extends BaseService { const [ name ] = args; this.visible.add(name); - } + }, }, { id: 'hide', @@ -93,7 +93,7 @@ class MinLogService extends BaseService { const [ name ] = args; this.visible.delete(name); - } + }, }, ]); } diff --git a/src/backend/src/modules/selfhosted/SelfHostedModule.js b/src/backend/src/modules/selfhosted/SelfHostedModule.js index 58081f111b..4952f1ce70 100644 --- a/src/backend/src/modules/selfhosted/SelfHostedModule.js +++ b/src/backend/src/modules/selfhosted/SelfHostedModule.js @@ -16,11 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const config = require("../../config"); +const { AdvancedBase } = require('@heyputer/putility'); +const config = require('../../config'); class SelfHostedModule extends AdvancedBase { - async install(context) { + async install (context) { const services = context.get('services'); const { SelfhostedService } = require('./SelfhostedService'); @@ -35,10 +35,10 @@ class SelfHostedModule extends AdvancedBase { const DevWatcherService = require('./DevWatcherService'); const path_ = require('path'); - const DevCreditService = require("./DevCreditService"); + const DevCreditService = require('./DevCreditService'); services.registerService('dev-credit', DevCreditService); - const { DBKVServiceWrapper } = require("../../services/repositories/DBKVStore/index.mjs"); + const { DBKVServiceWrapper } = require('../../services/repositories/DBKVStore/index.mjs'); services.registerService('puter-kvstore', DBKVServiceWrapper); // const MinLogService = require('./MinLogService'); @@ -56,14 +56,14 @@ class SelfHostedModule extends AdvancedBase { name: 'phoenix', directory: 'src/phoenix', env: { - PUTER_JS_URL: ({ global_config: config }) => config?.origin ? config.origin + '/puter.js/v2' : '', + PUTER_JS_URL: ({ global_config: config }) => config?.origin ? `${config.origin }/puter.js/v2` : '', }, }, { name: 'terminal', directory: 'src/terminal', env: { - PUTER_JS_URL: ({ global_config: config }) => config?.origin ? config.origin + '/puter.js/v2' : '', + PUTER_JS_URL: ({ global_config: config }) => config?.origin ? `${config.origin }/puter.js/v2` : '', }, }, { @@ -92,7 +92,7 @@ class SelfHostedModule extends AdvancedBase { }); } - const { ServeStaticFilesService } = require("./ServeStaticFilesService"); + const { ServeStaticFilesService } = require('./ServeStaticFilesService'); services.registerService('__serve-puterjs', ServeStaticFilesService, { directories: [ { diff --git a/src/backend/src/modules/selfhosted/SelfhostedService.js b/src/backend/src/modules/selfhosted/SelfhostedService.js index 3707e082df..9c079bb292 100644 --- a/src/backend/src/modules/selfhosted/SelfhostedService.js +++ b/src/backend/src/modules/selfhosted/SelfhostedService.js @@ -16,20 +16,20 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { Actor } = require("../../services/auth/Actor"); -const BaseService = require("../../services/BaseService"); -const { DB_WRITE } = require("../../services/database/consts"); -const { Context } = require("../../util/context"); +const { Actor } = require('../../services/auth/Actor'); +const BaseService = require('../../services/BaseService'); +const { DB_WRITE } = require('../../services/database/consts'); +const { Context } = require('../../util/context'); class SelfhostedService extends BaseService { static description = ` Registers drivers for self-hosted Puter instances. - ` + `; async _init () { this._register_commands(this.services.get('commands')); } - + _register_commands (commands) { const db = this.services.get('database').get(DB_WRITE, 'selfhosted'); commands.registerCommands('app', [ @@ -47,8 +47,8 @@ class SelfhostedService extends BaseService { } await db.write('UPDATE apps SET godmode = 1 WHERE uid = ?', [app_uid]); }); - } - } + }, + }, ]); commands.registerCommands('app', [ { @@ -65,8 +65,8 @@ class SelfhostedService extends BaseService { } await db.write('UPDATE apps SET godmode = 0 WHERE uid = ?', [app_uid]); }); - } - } + }, + }, ]); } } diff --git a/src/backend/src/modules/selfhosted/ServeSingeFileService.js b/src/backend/src/modules/selfhosted/ServeSingeFileService.js index 068b1011b9..5f631e5fe6 100644 --- a/src/backend/src/modules/selfhosted/ServeSingeFileService.js +++ b/src/backend/src/modules/selfhosted/ServeSingeFileService.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class ServeSingleFileService extends BaseService { async _init (args) { @@ -25,13 +25,13 @@ class ServeSingleFileService extends BaseService { } async ['__on_install.routes'] () { const { app } = this.services.get('web-server'); - + app.get(this.route, (req, res) => { return res.sendFile(this.path); }); } } -module.exports = { +module.exports = { ServeSingleFileService, }; diff --git a/src/backend/src/modules/selfhosted/ServeStaticFilesService.js b/src/backend/src/modules/selfhosted/ServeStaticFilesService.js index cbb9b717d4..baa1520adc 100644 --- a/src/backend/src/modules/selfhosted/ServeStaticFilesService.js +++ b/src/backend/src/modules/selfhosted/ServeStaticFilesService.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class ServeStaticFilesService extends BaseService { async _init (args) { diff --git a/src/backend/src/modules/template/TemplateModule.js b/src/backend/src/modules/template/TemplateModule.js index 805f112406..971cafdddf 100644 --- a/src/backend/src/modules/template/TemplateModule.js +++ b/src/backend/src/modules/template/TemplateModule.js @@ -17,11 +17,11 @@ * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); /** * This is a template module that you can copy and paste to create new modules. - * + * * This module is also included in `EssentialModules`, which means it will load * when Puter boots. If you're just testing something, you can add it here * temporarily. @@ -32,13 +32,13 @@ class TemplateModule extends AdvancedBase { const useapi = context.get('useapi'); const lib = require('./lib/__lib__.js'); - + // In extensions: use('workinprogress').hello_world(); // In services classes: see TemplateService.js - useapi.def(`workinprogress`, lib, { assign: true }); - + useapi.def('workinprogress', lib, { assign: true }); + useapi.def('core.context', require('../../util/context.js').Context); - + // === SERVICES === // const services = context.get('services'); @@ -49,5 +49,5 @@ class TemplateModule extends AdvancedBase { } module.exports = { - TemplateModule + TemplateModule, }; diff --git a/src/backend/src/modules/template/TemplateService.js b/src/backend/src/modules/template/TemplateService.js index ca4d354064..1cbd042baf 100644 --- a/src/backend/src/modules/template/TemplateService.js +++ b/src/backend/src/modules/template/TemplateService.js @@ -18,8 +18,8 @@ */ // TODO: import via `USE` static member -const BaseService = require("../../services/BaseService"); -const { Endpoint } = require("../../util/expressutil"); +const BaseService = require('../../services/BaseService'); +const { Endpoint } = require('../../util/expressutil'); /** * This is a template service that you can copy and paste to create new services. @@ -29,60 +29,60 @@ class TemplateService extends BaseService { static USE = { // - Defined by lib/__lib__.js, // - Exposed to `useapi` by TemplateModule.js - workinprogress: 'workinprogress' - } - + workinprogress: 'workinprogress', + }; + _construct () { // Use this override to initialize instance variables. } - + async _init () { // This is where you initialize the service and prepare // for the consolidation phase. - this.log.info("I am the template service."); + this.log.info('I am the template service.'); } - + /** * TemplateService listens to this event to provide an example endpoint */ ['__on_install.routes'] (_, { app }) { - this.log.info("TemplateService get the event for installing endpoint."); + this.log.info('TemplateService get the event for installing endpoint.'); Endpoint({ route: '/example-endpoint', methods: ['GET'], handler: async (req, res) => { res.send(this.workinprogress.hello_world()); - } + }, }).attach(app); // ^ Don't forget to attach the endpoint to the app! // it's very easy to forget this step. } - + /** * TemplateService listens to this event to provide an example event */ ['__on_boot.consolidation'] () { // At this stage, all services have been initialized and it is // safe to start emitting events. - this.log.info("TemplateService sees consolidation boot phase."); - + this.log.info('TemplateService sees consolidation boot phase.'); + const svc_event = this.services.get('event'); - + svc_event.on('template-service.hello', (_eventid, event_data) => { this.log.info('template-service said hello to itself; this is expected', { event_data, }); }); - + svc_event.emit('template-service.hello', { - message: 'Hello all you other services! I am the template service.' + message: 'Hello all you other services! I am the template service.', }); } /** * TemplateService listens to this event to show you that it's here */ ['__on_boot.activation'] () { - this.log.info("TemplateService sees activation boot phase."); + this.log.info('TemplateService sees activation boot phase.'); } /** @@ -94,6 +94,5 @@ class TemplateService extends BaseService { } module.exports = { - TemplateService + TemplateService, }; - diff --git a/src/backend/src/modules/template/lib/__lib__.js b/src/backend/src/modules/template/lib/__lib__.js index 77bba8ef7b..f37ca72d58 100644 --- a/src/backend/src/modules/template/lib/__lib__.js +++ b/src/backend/src/modules/template/lib/__lib__.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ diff --git a/src/backend/src/modules/template/lib/hello_world.js b/src/backend/src/modules/template/lib/hello_world.js index 8b9f2a4261..4842c08306 100644 --- a/src/backend/src/modules/template/lib/hello_world.js +++ b/src/backend/src/modules/template/lib/hello_world.js @@ -1,29 +1,28 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ - /** * This is a simple function that returns a string. * You can probably guess what string it returns. */ const hello_world = () => { - return "Hello, world!"; -} + return 'Hello, world!'; +}; module.exports = hello_world; diff --git a/src/backend/src/modules/test-config/TestConfigModule.js b/src/backend/src/modules/test-config/TestConfigModule.js index be6d0dab88..7000007272 100644 --- a/src/backend/src/modules/test-config/TestConfigModule.js +++ b/src/backend/src/modules/test-config/TestConfigModule.js @@ -1,4 +1,4 @@ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class TestConfigModule extends AdvancedBase { async install (context) { diff --git a/src/backend/src/modules/test-config/TestConfigReadService.js b/src/backend/src/modules/test-config/TestConfigReadService.js index 67bf3f8935..b5c4893543 100644 --- a/src/backend/src/modules/test-config/TestConfigReadService.js +++ b/src/backend/src/modules/test-config/TestConfigReadService.js @@ -1,10 +1,9 @@ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class TestConfigReadService extends BaseService { async _init () { - this.log.debug('test config value (should be abcdefg) is: ' + - this.global_config.testConfigValue, - ); + this.log.debug(`test config value (should be abcdefg) is: ${ + this.global_config.testConfigValue}`); } } diff --git a/src/backend/src/modules/test-config/TestConfigUpdateService.js b/src/backend/src/modules/test-config/TestConfigUpdateService.js index b3a34d57a1..566131cfba 100644 --- a/src/backend/src/modules/test-config/TestConfigUpdateService.js +++ b/src/backend/src/modules/test-config/TestConfigUpdateService.js @@ -1,10 +1,10 @@ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class TestConfigUpdateService extends BaseService { async _run_as_early_as_possible () { const config = this.global_config; config.__set_config_object__({ - testConfigValue: 'abcdefg' + testConfigValue: 'abcdefg', }); } } diff --git a/src/backend/src/modules/test-drivers/TestAssetHostService.js b/src/backend/src/modules/test-drivers/TestAssetHostService.js index d9bfb26f4e..93167de62f 100644 --- a/src/backend/src/modules/test-drivers/TestAssetHostService.js +++ b/src/backend/src/modules/test-drivers/TestAssetHostService.js @@ -1,35 +1,34 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const BaseService = require("../../services/BaseService"); +const BaseService = require('../../services/BaseService'); class TestAssetHostService extends BaseService { async ['__on_install.routes'] () { const { app } = this.services.get('web-server'); const path_ = require('node:path'); - + app.use('/test-assets', require('express').static( - path_.join(__dirname, 'assets') - )); + path_.join(__dirname, 'assets'))); } } module.exports = { - TestAssetHostService + TestAssetHostService, }; diff --git a/src/backend/src/modules/test-drivers/TestDriversModule.js b/src/backend/src/modules/test-drivers/TestDriversModule.js index bf2b441e7f..86d56e4031 100644 --- a/src/backend/src/modules/test-drivers/TestDriversModule.js +++ b/src/backend/src/modules/test-drivers/TestDriversModule.js @@ -1,31 +1,31 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); +const { AdvancedBase } = require('@heyputer/putility'); class TestDriversModule extends AdvancedBase { async install (context) { const services = context.get('services'); - - const { TestAssetHostService } = require('./TestAssetHostService') + + const { TestAssetHostService } = require('./TestAssetHostService'); services.registerService('__test-assets', TestAssetHostService); - + const { TestImageService } = require('./TestImageService'); services.registerService('test-image', TestImageService); } diff --git a/src/backend/src/modules/test-drivers/TestImageService.js b/src/backend/src/modules/test-drivers/TestImageService.js index f11930ffb5..f646794c18 100644 --- a/src/backend/src/modules/test-drivers/TestImageService.js +++ b/src/backend/src/modules/test-drivers/TestImageService.js @@ -1,26 +1,26 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const config = require("../../config"); -const BaseService = require("../../services/BaseService"); -const { TypedValue } = require("../../services/drivers/meta/Runtime"); -const { buffer_to_stream } = require("../../util/streamutil"); +const config = require('../../config'); +const BaseService = require('../../services/BaseService'); +const { TypedValue } = require('../../services/drivers/meta/Runtime'); +const { buffer_to_stream } = require('../../util/streamutil'); const PUBLIC_DOMAIN_IMAGES = [ { @@ -34,7 +34,7 @@ class TestImageService extends BaseService { async ['__on_driver.register.interfaces'] () { const svc_registry = this.services.get('registry'); const col_interfaces = svc_registry.get('interfaces'); - + col_interfaces.set('test-image', { methods: { echo_image: { @@ -46,24 +46,24 @@ class TestImageService extends BaseService { result: { type: { $: 'stream', - content_type: 'image' + content_type: 'image', }, }, }, get_image: { parameters: { source_type: { - type: 'string' + type: 'string', }, }, result: { type: { $: 'stream', - content_type: 'image' - } - } - } - } + content_type: 'image', + }, + }, + }, + }, }); } @@ -71,7 +71,7 @@ class TestImageService extends BaseService { ['version']: { get_version () { return 'v1.0.0'; - } + }, }, ['test-image']: { async echo_image ({ @@ -80,7 +80,7 @@ class TestImageService extends BaseService { const stream = await source.get('stream'); return new TypedValue({ $: 'stream', - content_type: 'image/jpeg' + content_type: 'image/jpeg', }, stream); }, async get_image ({ @@ -94,11 +94,11 @@ class TestImageService extends BaseService { }, `${config.origin}/test-assets/${image.file}`); } throw new Error('not implemented yet'); - } + }, }, - } + }; } module.exports = { - TestImageService + TestImageService, }; diff --git a/src/backend/src/modules/web/APIErrorService.js b/src/backend/src/modules/web/APIErrorService.js index c41bda205f..ecdee4cbf6 100644 --- a/src/backend/src/modules/web/APIErrorService.js +++ b/src/backend/src/modules/web/APIErrorService.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const BaseService = require("../../services/BaseService"); +const APIError = require('../../api/APIError'); +const BaseService = require('../../services/BaseService'); /** * @typedef {Object} ErrorSpec @@ -30,7 +30,7 @@ const BaseService = require("../../services/BaseService"); /** * The APIErrorService class provides a mechanism for registering and managing * error codes and messages which may be sent to clients. - * + * * This allows for a single source-of-truth for error codes and messages that * are used by multiple services. */ @@ -43,10 +43,10 @@ class APIErrorService extends BaseService { // Hardcoded error codes from before this service was created static codes = APIError.codes; - + /** * Registers API error codes. - * + * * @param {Object.} codes - A map of error codes to error specifications */ register (codes) { @@ -54,7 +54,7 @@ class APIErrorService extends BaseService { this.codes[code] = codes[code]; } } - + create (code, fields) { const error_spec = this.codes[code]; if ( ! error_spec ) { @@ -62,7 +62,7 @@ class APIErrorService extends BaseService { code, }); } - + return new APIError(error_spec.status, error_spec.message, null, { ...fields, code, diff --git a/src/backend/src/modules/web/SocketioService.js b/src/backend/src/modules/web/SocketioService.js index 30017b1d9e..f1651ab737 100644 --- a/src/backend/src/modules/web/SocketioService.js +++ b/src/backend/src/modules/web/SocketioService.js @@ -1,18 +1,18 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ @@ -32,12 +32,12 @@ class SocketioService extends BaseService { /** * Initializes socket.io - * + * * @evtparam server The server to attach socket.io to. */ ['__on_install.socketio'] (_, { server }) { const require = this.require; - + const socketio = require('socket.io'); /** * @type {import('socket.io').Server} @@ -45,14 +45,13 @@ class SocketioService extends BaseService { this.io = socketio(server, { cors: { origin: '*', - } + }, }); } - /** * Sends a message to specified socket(s) or room(s) - * + * * @param {Array|Object} socket_specifiers - Single or array of objects specifying target sockets/rooms * @param {string} key - The event key/name to emit * @param {*} data - The data payload to send @@ -62,21 +61,21 @@ class SocketioService extends BaseService { if ( ! Array.isArray(socket_specifiers) ) { socket_specifiers = [socket_specifiers]; } - + for ( const socket_specifier of socket_specifiers ) { if ( socket_specifier.room ) { this.io.to(socket_specifier.room).emit(key, data); } else if ( socket_specifier.socket ) { - const io = this.io.sockets.sockets.get(socket_specifier.socket) + const io = this.io.sockets.sockets.get(socket_specifier.socket); if ( ! io ) continue; io.emit(key, data); } } } - + /** * Checks if the specified socket or room exists - * + * * @param {Object} socket_specifier - The socket specifier object * @returns {boolean} True if the socket exists, false otherwise */ diff --git a/src/backend/src/modules/web/WebModule.js b/src/backend/src/modules/web/WebModule.js index 9c9bb5a824..56d3f388f6 100644 --- a/src/backend/src/modules/web/WebModule.js +++ b/src/backend/src/modules/web/WebModule.js @@ -1,24 +1,24 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { RuntimeModule } = require("../../extension/RuntimeModule.js"); +const { AdvancedBase } = require('@heyputer/putility'); +const { RuntimeModule } = require('../../extension/RuntimeModule.js'); /** * This module initializes a pre-configured web server and socket.io server. @@ -30,25 +30,25 @@ class WebModule extends AdvancedBase { // === LIBS === // const useapi = context.get('useapi'); useapi.def('web', require('./lib/__lib__.js'), { assign: true }); - + // Prevent extensions from loading incompatible versions of express useapi.def('web.express', require('express')); - + // Extension compatibility const runtimeModule = new RuntimeModule({ name: 'web' }); context.get('runtime-modules').register(runtimeModule); runtimeModule.exports = useapi.use('web'); - + // === SERVICES === // const services = context.get('services'); - - const SocketioService = require("./SocketioService"); + + const SocketioService = require('./SocketioService'); services.registerService('socketio', SocketioService); - - const WebServerService = require("./WebServerService"); + + const WebServerService = require('./WebServerService'); services.registerService('web-server', WebServerService); - - const APIErrorService = require("./APIErrorService"); + + const APIErrorService = require('./APIErrorService'); services.registerService('api-error', APIErrorService); } } diff --git a/src/backend/src/modules/web/WebServerService.d.ts b/src/backend/src/modules/web/WebServerService.d.ts index 8d1ddf5a24..77a4c10dc5 100644 --- a/src/backend/src/modules/web/WebServerService.d.ts +++ b/src/backend/src/modules/web/WebServerService.d.ts @@ -9,12 +9,12 @@ export class WebServerService extends BaseService { * Allow requests with undefined Origin header for a specific route. * @param route The route (string or RegExp) to allow. */ - allow_undefined_origin(route: string | RegExp): void; + allow_undefined_origin (route: string | RegExp): void; /** * Returns the underlying HTTP server instance. */ - get_server(): Server; + get_server (): Server; } export = WebServerService; \ No newline at end of file diff --git a/src/backend/src/modules/web/WebServerService.js b/src/backend/src/modules/web/WebServerService.js index d7b0beaab0..fd26067447 100644 --- a/src/backend/src/modules/web/WebServerService.js +++ b/src/backend/src/modules/web/WebServerService.js @@ -53,7 +53,7 @@ class WebServerService extends BaseService { allowedRoutesWithUndefinedOrigins = []; - allow_undefined_origin(route) { + allow_undefined_origin (route) { this.allowedRoutesWithUndefinedOrigins.push(route); } @@ -65,7 +65,7 @@ class WebServerService extends BaseService { * @private */ // comment above line 44 in WebServerService.js - async ['__on_boot.consolidation']() { + async ['__on_boot.consolidation'] () { const app = this.app; const services = this.services; await services.emit('install.middlewares.early', { app }); @@ -85,7 +85,7 @@ class WebServerService extends BaseService { this.log.debug('web server setup done'); } - install_post_middlewares_({ app }) { + install_post_middlewares_ ({ app }) { app.use(async (req, res, next) => { const svc_event = this.services.get('event'); @@ -93,7 +93,7 @@ class WebServerService extends BaseService { req, res, end_: false, - end() { + end () { this.end_ = true; }, }; @@ -109,7 +109,7 @@ class WebServerService extends BaseService { * * @returns {Promise} A promise that resolves once the server is started. */ - async ['__on_boot.activation']() { + async ['__on_boot.activation'] () { const services = this.services; await services.emit('start.webserver'); await services.emit('ready.webserver'); @@ -124,7 +124,7 @@ class WebServerService extends BaseService { * * @return {Promise} A promise that resolves when the server is up and running. */ - async ['__on_start.webserver']() { + async ['__on_start.webserver'] () { await es_import_promise; // error handling middleware goes last, as per the @@ -155,7 +155,7 @@ class WebServerService extends BaseService { for ( let i = 0 ; i < ports_to_try.length ; i++ ) { const port = ports_to_try[i]; const is_last_port = i === ports_to_try.length - 1; - if ( auto_port ) this.log.debug('trying port: ' + port); + if ( auto_port ) this.log.debug(`trying port: ${ port}`); try { server = http.createServer(this.app).listen(port); server.timeout = 1000 * 60 * 60 * 2; // 2 hours @@ -163,8 +163,8 @@ class WebServerService extends BaseService { await new Promise((rslv, rjct) => { server.on('error', e => { if ( e.code === 'EADDRINUSE' ) { - if ( ! is_last_port && e.code === 'EADDRINUSE' ) { - this.log.info('port in use: ' + port); + if ( !is_last_port && e.code === 'EADDRINUSE' ) { + this.log.info(`port in use: ${ port}`); should_continue = true; } rslv(); @@ -187,8 +187,8 @@ class WebServerService extends BaseService { }); if ( should_continue ) continue; } catch (e) { - if ( ! is_last_port && e.code === 'EADDRINUSE' ) { - this.log.info('port in use:' + port); + if ( !is_last_port && e.code === 'EADDRINUSE' ) { + this.log.info(`port in use:${ port}`); continue; } throw e; @@ -202,7 +202,7 @@ class WebServerService extends BaseService { // Open the browser to the URL of Puter // (if we are in development mode only) - if ( config.env === 'dev' && ! config.no_browser_launch ) { + if ( config.env === 'dev' && !config.no_browser_launch ) { try { const openModule = await import('open'); openModule.default(url); @@ -310,7 +310,7 @@ class WebServerService extends BaseService { * @param {object} services - An object containing all services available to the web server. * @returns {Promise} A promise that resolves when the web server is fully started. */ - get_server() { + get_server () { return this.server_; } @@ -319,7 +319,7 @@ class WebServerService extends BaseService { * * @param {Object} services - An object containing all services. */ - async _init() { + async _init () { const app = express(); this.app = app; @@ -375,12 +375,12 @@ class WebServerService extends BaseService { // remove `puter.auth.*` query params const safe_url = (u => { // We need to prepend an arbitrary domain to the URL - const url = new URL('https://example.com' + u); + const url = new URL(`https://example.com${ u}`); const search = url.searchParams; for ( const key of search.keys() ) { if ( key.startsWith('puter.auth.') ) search.delete(key); } - return url.pathname + '?' + search.toString(); + return `${url.pathname }?${ search.toString()}`; })(fields.url); fields.url = safe_url; // re-write message @@ -447,7 +447,7 @@ class WebServerService extends BaseService { }); })(); - app.use(async function(req, res, next) { + app.use(async function (req, res, next) { // Express does not document that this can be undefined. // The browser likely doesn't follow the HTTP/1.1 spec // (bot client?) and express is handling this badly by @@ -467,7 +467,7 @@ class WebServerService extends BaseService { const allowedDomains = [ config.domain.toLowerCase(), config.static_hosting_domain.toLowerCase(), - 'at.' + config.static_hosting_domain.toLowerCase(), + `at.${ config.static_hosting_domain.toLowerCase()}`, ]; if ( config.allow_nipio_domains ) { @@ -477,7 +477,7 @@ class WebServerService extends BaseService { // Retrieve the Host header and ensure it's in a valid format const hostHeader = req.headers.host; - if ( ! config.allow_no_host_header && ! hostHeader ) { + if ( !config.allow_no_host_header && !hostHeader ) { return res.status(400).send('Missing Host header.'); } @@ -490,7 +490,7 @@ class WebServerService extends BaseService { const hostName = hostHeader.split(':')[0].trim().toLowerCase(); // Check if the hostname matches any of the allowed domains or is a subdomain of an allowed domain - if ( allowedDomains.some(allowedDomain => hostName === allowedDomain || hostName.endsWith('.' + allowedDomain)) ) { + if ( allowedDomains.some(allowedDomain => hostName === allowedDomain || hostName.endsWith(`.${ allowedDomain}`)) ) { next(); // Proceed if the host is valid } else { if ( ! config.custom_domains_enabled ) { @@ -585,7 +585,7 @@ class WebServerService extends BaseService { app.disable('x-powered-by'); // remove object and array query parameters - app.use(function(req, res, next) { + app.use(function (req, res, next) { for ( let k in req.query ) { if ( req.query[k] === undefined || req.query[k] === null ) { continue; @@ -600,30 +600,30 @@ class WebServerService extends BaseService { }); const uaParser = require('ua-parser-js'); - app.use(function(req, res, next) { + app.use(function (req, res, next) { const ua_header = req.headers['user-agent']; const ua = uaParser(ua_header); req.ua = ua; next(); }); - app.use(function(req, res, next) { + app.use(function (req, res, next) { req.co_isolation_enabled = ['Chrome', 'Edge'].includes(req.ua.browser.name) && (Number(req.ua.browser.major) >= 110); next(); }); - app.use(function(req, res, next) { + app.use(function (req, res, next) { const origin = req.headers.origin; const is_site = req.hostname.endsWith(config.static_hosting_domain) || req.hostname === 'docs.puter.com' ; - const is_popup = !! req.query.embedded_in_popup; - const is_parent_co = !! req.query.cross_origin_isolated; - const is_app = !! req.query['puter.app_instance_id']; + const is_popup = !!req.query.embedded_in_popup; + const is_parent_co = !!req.query.cross_origin_isolated; + const is_app = !!req.query['puter.app_instance_id']; const co_isolation_okay = (!is_popup || is_parent_co) && @@ -679,7 +679,7 @@ class WebServerService extends BaseService { }); } - _register_commands(commands) { + _register_commands (commands) { commands.registerCommands('web', [ { id: 'dismiss', @@ -706,7 +706,7 @@ class WebServerService extends BaseService { * @private */ // comment above line 497 - print_puter_logo_() { + print_puter_logo_ () { const realConsole = globalThis.original_console_object; if ( this.global_config.env !== 'dev' ) return; const logos = require('../../fun/logos.js'); @@ -726,7 +726,7 @@ class WebServerService extends BaseService { lines[i] = ' '.repeat(pad_left) + lines[i] + ' '.repeat(pad_right); } const txt = lines.join('\n'); - realConsole.log('\n\x1B[34;1m' + txt + '\x1B[0m\n'); + realConsole.log(`\n\x1B[34;1m${ txt }\x1B[0m\n`); } if ( config.os.archbtw ) { realConsole.log('\x1B[34;1mPuter is running on Arch btw\x1B[0m'); diff --git a/src/backend/src/modules/web/lib/__lib__.js b/src/backend/src/modules/web/lib/__lib__.js index 7d8007c8cb..bd9e95dd6b 100644 --- a/src/backend/src/modules/web/lib/__lib__.js +++ b/src/backend/src/modules/web/lib/__lib__.js @@ -1,23 +1,23 @@ /* * Copyright (C) 2024-present Puter Technologies Inc. - * + * * This file is part of Puter. - * + * * Puter is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ module.exports = { - eggspress: require("./eggspress"), - api_error_handler: require("./api_error_handler"), + eggspress: require('./eggspress'), + api_error_handler: require('./api_error_handler'), }; diff --git a/src/backend/src/modules/web/lib/api_error_handler.js b/src/backend/src/modules/web/lib/api_error_handler.js index a9844f7b39..8b8576f5d9 100644 --- a/src/backend/src/modules/web/lib/api_error_handler.js +++ b/src/backend/src/modules/web/lib/api_error_handler.js @@ -22,54 +22,54 @@ const APIError = require('../../../api/APIError.js'); * api_error_handler() is an express error handler for API errors. * It adheres to the express error handler signature and should be * used as the last middleware in an express app. - * + * * Since Express 5 is not yet released, this function is used by * eggspress() to handle errors instead of as a middleware. - * - * @param {*} err - * @param {*} req - * @param {*} res - * @param {*} next - * @returns + * + * @param {*} err + * @param {*} req + * @param {*} res + * @param {*} next + * @returns */ module.exports = function api_error_handler (err, req, res, next) { - if (res.headersSent) { - console.error('error after headers were sent:', err); - return next(err) - } + if ( res.headersSent ) { + console.error('error after headers were sent:', err); + return next(err); + } + + // API errors might have a response to help the + // developer resolve the issue. + if ( err instanceof APIError ) { + return err.write(res); + } - // API errors might have a response to help the - // developer resolve the issue. - if ( err instanceof APIError ) { - return err.write(res); - } + if ( + typeof err === 'object' && + !(err instanceof Error) && + err.hasOwnProperty('message') + ) { + const apiError = APIError.create(400, err); + return apiError.write(res); + } - if ( - typeof err === 'object' && - ! (err instanceof Error) && - err.hasOwnProperty('message') - ) { - const apiError = APIError.create(400, err); - return apiError.write(res); - } + console.error('internal server error:', err); - console.error('internal server error:', err); + const services = globalThis.services; + if ( services && services.has('alarm') ) { + const alarm = services.get('alarm'); + alarm.create('api_error_handler', err.message, { + error: err, + url: req.url, + method: req.method, + body: req.body, + headers: req.headers, + }); + } - const services = globalThis.services; - if ( services && services.has('alarm') ) { - const alarm = services.get('alarm'); - alarm.create('api_error_handler', err.message, { - error: err, - url: req.url, - method: req.method, - body: req.body, - headers: req.headers, - }); - } + req.__error_handled = true; - req.__error_handled = true; - - // Other errors should provide as little information - // to the client as possible for security reasons. - return res.send(500, 'Internal Server Error'); + // Other errors should provide as little information + // to the client as possible for security reasons. + return res.send(500, 'Internal Server Error'); }; diff --git a/src/backend/src/monitor/PerformanceMonitor.js b/src/backend/src/monitor/PerformanceMonitor.js index 03efe3779f..6792f330d0 100644 --- a/src/backend/src/monitor/PerformanceMonitor.js +++ b/src/backend/src/monitor/PerformanceMonitor.js @@ -16,8 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const config = require("../config"); -const BaseService = require("../services/BaseService"); +const config = require('../config'); +const BaseService = require('../services/BaseService'); class Metric { constructor (windowSize) { @@ -64,7 +64,7 @@ class Metric { if ( this.cumulativeAverage ) { metrics.push({ - MetricName: prefix + '.' + 'cumulative-avg', + MetricName: `${prefix }.` + 'cumulative-avg', Value: this.cumulativeAverage, Timestamp, Unit: 'Milliseconds', @@ -74,7 +74,7 @@ class Metric { if ( this.windowAverage && this.count >= this.WINDOW_SIZE ) { metrics.push({ - MetricName: prefix + '.' + 'window-avg', + MetricName: `${prefix }.` + 'window-avg', Value: this.windowAverage, Timestamp, Unit: 'Milliseconds', @@ -96,7 +96,8 @@ class PerformanceMonitorContext { this.stamp('monitor-created'); } - branch () {} + branch () { + } stamp (name) { if ( ! name ) { @@ -105,7 +106,7 @@ class PerformanceMonitorContext { } this.stamps.push({ name, - ts: Date.now() + ts: Date.now(), }); } @@ -113,11 +114,11 @@ class PerformanceMonitorContext { this.stamps.push({ name, start: Date.now(), - }) + }); } end () { - this.stamp("end"); + this.stamp('end'); this.performanceMonitor.logMonitorContext(this); } } @@ -138,7 +139,6 @@ class PerformanceMonitor extends BaseService { this.cw = new AWS.CloudWatch(config.cloudwatch); } - if ( config.monitor ) { this.config = config.monitor; } @@ -152,7 +152,7 @@ class PerformanceMonitor extends BaseService { createContext (name) { return new PerformanceMonitorContext({ performanceMonitor: this, - name + name, }); } @@ -161,7 +161,7 @@ class PerformanceMonitor extends BaseService { this.performanceMetrics[ctx.name] = new Metric(config.windowSize ?? 30); } - + const metricsToUpdate = {}; // Update averaging metrics @@ -190,7 +190,7 @@ class PerformanceMonitor extends BaseService { } if ( ! config.performance_monitors_stdout ) return; - + // Write to stout { console.log('[Monitor Snapshot]', ctx.name); @@ -198,8 +198,7 @@ class PerformanceMonitor extends BaseService { for ( const stamp of ctx.stamps ) { let start = stamp.start ?? begin.ts; let end = stamp.end ?? stamp.ts; - console.log('|', stamp.name, - (end - start) + 'ms') + console.log('|', stamp.name, `${end - start }ms`); } } } @@ -220,11 +219,10 @@ class PerformanceMonitor extends BaseService { for ( let key in this.performanceMetrics ) { const prefix = key.replace(/\s+/g, '-'); const metric = this.performanceMetrics[key]; - + MetricData.push(...metric.getCloudwatchMetrics(prefix)); } - const Dimensions = [ { Name: 'server-id', @@ -243,7 +241,7 @@ class PerformanceMonitor extends BaseService { if ( Number.isNaN(value) ) continue; const prefix = key.replace(/\s+/g, '-'); MetricData.push({ - MetricName: prefix + '.operations', + MetricName: `${prefix }.operations`, Unit: 'Count/Second', Value: value, Dimensions, @@ -270,10 +268,8 @@ class PerformanceMonitor extends BaseService { await this.cw.putMetricData(params).promise(); } catch (e) { // TODO: alarm condition - console.error( - 'Failed to send metrics to CloudWatch', - e - ) + console.error('Failed to send metrics to CloudWatch', + e); } } } diff --git a/src/backend/src/om/IdentifierUtil.js b/src/backend/src/om/IdentifierUtil.js index 9f1e88b500..a2540f2822 100644 --- a/src/backend/src/om/IdentifierUtil.js +++ b/src/backend/src/om/IdentifierUtil.js @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../traits/WeakConstructorFeature"); -const { Eq, And } = require("./query/query"); -const { Entity } = require("./entitystorage/Entity"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../traits/WeakConstructorFeature'); +const { Eq, And } = require('./query/query'); +const { Entity } = require('./entitystorage/Entity'); class IdentifierUtil extends AdvancedBase { static FEATURES = [ new WeakConstructorFeature(), - ] + ]; async detect_identifier (object, allow_mutation = false) { const redundant_identifiers = this.om.redundant_identifiers ?? []; @@ -34,7 +34,7 @@ class IdentifierUtil extends AdvancedBase { key_set = Array.isArray(key_set) ? key_set : [key_set]; key_set.sort(); - for ( let i=0 ; i < key_set.length ; i++ ) { + for ( let i = 0 ; i < key_set.length ; i++ ) { const key = key_set[i]; const has_key = object instanceof Entity ? await object.has(key) : object[key] !== undefined; @@ -71,5 +71,5 @@ class IdentifierUtil extends AdvancedBase { } module.exports = { - IdentifierUtil + IdentifierUtil, }; diff --git a/src/backend/src/om/definitions/Mapping.js b/src/backend/src/om/definitions/Mapping.js index 9a929b2efc..b403da708c 100644 --- a/src/backend/src/om/definitions/Mapping.js +++ b/src/backend/src/om/definitions/Mapping.js @@ -16,11 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); -const { Property } = require("./Property"); -const { Entity } = require("../entitystorage/Entity"); -const FSNodeContext = require("../../filesystem/FSNodeContext"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); +const { Property } = require('./Property'); +const { Entity } = require('../entitystorage/Entity'); +const FSNodeContext = require('../../filesystem/FSNodeContext'); /** * An instance of Mapping wraps every definition in ../mappings before @@ -35,7 +35,7 @@ class Mapping extends AdvancedBase { // examples where this is typically not possible. // However, javascript is magic, and we do what we want. new WeakConstructorFeature(), - ] + ]; static create (context, data) { const properties = {}; @@ -86,7 +86,7 @@ class Mapping extends AdvancedBase { // This is for reference properties to remove sensitive // information in case a decorator added the real object. if ( - ( ! sanitized ) && + ( !sanitized ) && typeof value === 'object' && value !== null && prop.descriptor.permissible_subproperties ) { @@ -109,5 +109,5 @@ class Mapping extends AdvancedBase { } module.exports = { - Mapping + Mapping, }; diff --git a/src/backend/src/om/definitions/PropType.js b/src/backend/src/om/definitions/PropType.js index fc75ffdcb3..1b5739630c 100644 --- a/src/backend/src/om/definitions/PropType.js +++ b/src/backend/src/om/definitions/PropType.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); class PropType extends AdvancedBase { static FEATURES = [ new WeakConstructorFeature(), - ] + ]; static create (context, data, k) { const chains = {}; diff --git a/src/backend/src/om/definitions/Property.js b/src/backend/src/om/definitions/Property.js index 72cd9766fd..741a2b83ff 100644 --- a/src/backend/src/om/definitions/Property.js +++ b/src/backend/src/om/definitions/Property.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); class Property extends AdvancedBase { static FEATURES = [ new WeakConstructorFeature(), - ] + ]; static create (context, name, descriptor) { // Adapt descriptor @@ -94,5 +94,5 @@ class Property extends AdvancedBase { } module.exports = { - Property + Property, }; diff --git a/src/backend/src/om/entitystorage/AppES.js b/src/backend/src/om/entitystorage/AppES.js index 8aadc5d680..6ccd2d6039 100644 --- a/src/backend/src/om/entitystorage/AppES.js +++ b/src/backend/src/om/entitystorage/AppES.js @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { app_name_exists, refresh_apps_cache } = require("../../helpers"); +const APIError = require('../../api/APIError'); +const { app_name_exists, refresh_apps_cache } = require('../../helpers'); -const { AppUnderUserActorType } = require("../../services/auth/Actor"); -const { DB_WRITE } = require("../../services/database/consts"); -const { Context } = require("../../util/context"); -const { origin_from_url } = require("../../util/urlutil"); -const { Eq, Like, Or, And } = require("../query/query"); -const { BaseES } = require("./BaseES"); +const { AppUnderUserActorType } = require('../../services/auth/Actor'); +const { DB_WRITE } = require('../../services/database/consts'); +const { Context } = require('../../util/context'); +const { origin_from_url } = require('../../util/urlutil'); +const { Eq, Like, Or, And } = require('../query/query'); +const { BaseES } = require('./BaseES'); const uuidv4 = require('uuid').v4; @@ -79,25 +79,23 @@ class AppES extends BaseES { })); } - options.predicate = options.predicate.and( - new Or({ - children: [ - new Eq({ - key: 'approved_for_listing', - value: 1, - }), - new Eq({ - key: 'owner', - value: user.id, - }), - ...additional, - ], - }), - ); + options.predicate = options.predicate.and(new Or({ + children: [ + new Eq({ + key: 'approved_for_listing', + value: 1, + }), + new Eq({ + key: 'owner', + value: user.id, + }), + ...additional, + ], + })); return await this.upstream.select(options); }, - + /** * Creates or updates an application with proper name handling and associations * @param {Object} entity - Application entity to upsert @@ -107,7 +105,7 @@ class AppES extends BaseES { async upsert (entity, extra) { if ( await app_name_exists(await entity.get('name')) ) { const { old_entity } = extra; - const is_name_change = ( ! old_entity ) || + const is_name_change = ( !old_entity ) || ( await old_entity.get('name') !== await entity.get('name') ); if ( is_name_change && extra?.options?.dedupe_name ) { const base = await entity.get('name'); @@ -115,17 +113,17 @@ class AppES extends BaseES { while ( await app_name_exists(`${base}-${number}`) ) { number++; } - await entity.set('name', `${base}-${number}`) + await entity.set('name', `${base}-${number}`); } else if ( is_name_change ) { // The name might be taken because it's the old name // of this same app. If it is, the app takes it back. const svc_oldAppName = this.context.get('services').get('old-app-name'); const name_info = await svc_oldAppName.check_app_name(await entity.get('name')); - if ( ! name_info || name_info.app_uid !== await entity.get('uid') ) { + if ( !name_info || name_info.app_uid !== await entity.get('uid') ) { // Throw error because the name really is taken throw APIError.create('app_name_already_in_use', null, { - name: await entity.get('name') + name: await entity.get('name'), }); } @@ -142,25 +140,23 @@ class AppES extends BaseES { // Remove old file associations (if applicable) if ( extra.old_entity ) { - await this.db.write( - `DELETE FROM app_filetype_association WHERE app_id = ?`, - [insert_id] - ); + await this.db.write('DELETE FROM app_filetype_association WHERE app_id = ?', + [insert_id]); } // Add file associations (if applicable) const filetype_associations = await entity.get('filetype_associations'); if ( (a => a && a.length > 0)(filetype_associations) ) { const stmt = - `INSERT INTO app_filetype_association ` + - `(app_id, type) VALUES ` + - filetype_associations.map(() => '(?, ?)').join(', '); + 'INSERT INTO app_filetype_association ' + + `(app_id, type) VALUES ${ + filetype_associations.map(() => '(?, ?)').join(', ')}`; const rows = filetype_associations.map(a => [insert_id, a.toLowerCase()]); await this.db.write(stmt, rows.flat()); } const has_new_icon = - ( ! extra.old_entity ) || ( + ( !extra.old_entity ) || ( await entity.get('icon') !== await extra.old_entity.get('icon') ); @@ -172,7 +168,7 @@ class AppES extends BaseES { }; await svc_event.emit('app.new-icon', event); if ( event.url ) { - await entity.set('icon') + await entity.set('icon'); } } @@ -193,10 +189,8 @@ class AppES extends BaseES { // Associate app with subdomain (if applicable) if ( subdomain_id ) { - await this.db.write( - `UPDATE subdomains SET associated_app_id = ? WHERE id = ?`, - [insert_id, subdomain_id] - ); + await this.db.write('UPDATE subdomains SET associated_app_id = ? WHERE id = ?', + [insert_id, subdomain_id]); } const owner = extra.old_entity @@ -235,16 +229,12 @@ class AppES extends BaseES { const recurse = async (predicate) => { if ( predicate instanceof Or ) { return new Or({ - children: await Promise.all( - predicate.children.map(recurse) - ), + children: await Promise.all(predicate.children.map(recurse)), }); } if ( predicate instanceof And ) { return new And({ - children: await Promise.all( - predicate.children.map(recurse) - ), + children: await Promise.all(predicate.children.map(recurse)), }); } if ( predicate instanceof Eq ) { @@ -267,22 +257,18 @@ class AppES extends BaseES { */ async read_transform (entity) { // Add file associations - const rows = await this.db.read( - `SELECT type FROM app_filetype_association WHERE app_id = ?`, - [entity.private_meta.mysql_id] - ); + const rows = await this.db.read('SELECT type FROM app_filetype_association WHERE app_id = ?', + [entity.private_meta.mysql_id]); entity.set('filetype_associations', rows.map(row => row.type)); const svc_appInformation = this.context.get('services').get('app-information'); - const stats = await svc_appInformation.get_stats(await entity.get('uid'), {period: Context.get('es_params')?.stats_period, grouping: Context.get('es_params')?.stats_grouping, created_at: await entity.get('created_at')}); + const stats = await svc_appInformation.get_stats(await entity.get('uid'), { period: Context.get('es_params')?.stats_period, grouping: Context.get('es_params')?.stats_grouping, created_at: await entity.get('created_at') }); entity.set('stats', stats); entity.set('created_from_origin', await (async () => { const svc_auth = this.context.get('services').get('auth'); try { - const origin = origin_from_url( - await entity.get('index_url') - ); + const origin = origin_from_url(await entity.get('index_url')); const expected_uid = await svc_auth.app_uid_from_origin(origin); return expected_uid === await entity.get('uid') ? origin : null ; @@ -295,7 +281,7 @@ class AppES extends BaseES { // Check if the user is the owner const is_owner = await (async () => { let owner = await entity.get('owner'); - + // TODO: why does this happen? if ( typeof owner === 'number' ) { owner = { id: owner }; @@ -342,29 +328,26 @@ class AppES extends BaseES { let subdomain_id; if ( await entity.get('source_directory') ) { - await ( - await entity.get('source_directory') + await (await entity.get('source_directory') ).fetchEntry(); const subdomain = await entity.get('subdomain'); const user = Context.get('user'); - let subdomain_res = await this.db.write( - `INSERT ${this.db.case({ - mysql: 'IGNORE', - sqlite: 'OR IGNORE', - })} INTO subdomains + let subdomain_res = await this.db.write(`INSERT ${this.db.case({ + mysql: 'IGNORE', + sqlite: 'OR IGNORE', + })} INTO subdomains (subdomain, user_id, root_dir_id, uuid) VALUES ( ?, ?, ?, ?)`, - [ - //subdomain - subdomain, - //user_id - user.id, - //root_dir_id - (await entity.get('source_directory')).mysql_id, - //uuid, `sd` stands for subdomain - 'sd-' + uuidv4() - ] - ); + [ + //subdomain + subdomain, + //user_id + user.id, + //root_dir_id + (await entity.get('source_directory')).mysql_id, + //uuid, `sd` stands for subdomain + `sd-${ uuidv4()}`, + ]); subdomain_id = subdomain_res.insertId; } diff --git a/src/backend/src/om/entitystorage/AppLimitedES.js b/src/backend/src/om/entitystorage/AppLimitedES.js index 540edc0e0a..a8fe6dd697 100644 --- a/src/backend/src/om/entitystorage/AppLimitedES.js +++ b/src/backend/src/om/entitystorage/AppLimitedES.js @@ -16,11 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AppUnderUserActorType } = require("../../services/auth/Actor"); -const { Context } = require("../../util/context"); -const { Eq, Or } = require("../query/query"); -const { BaseES } = require("./BaseES"); -const { Entity } = require("./Entity"); +const { AppUnderUserActorType } = require('../../services/auth/Actor'); +const { Context } = require('../../util/context'); +const { Eq, Or } = require('../query/query'); +const { BaseES } = require('./BaseES'); +const { Entity } = require('./Entity'); class AppLimitedES extends BaseES { @@ -75,7 +75,7 @@ class AppLimitedES extends BaseES { if ( app_owner instanceof Entity ) { app_owner_id = app_owner.private_meta.mysql_id; } - if ( ( ! app_owner ) || app_owner_id !== app.id ) { + if ( ( !app_owner ) || app_owner_id !== app.id ) { return null; } } diff --git a/src/backend/src/om/entitystorage/BaseES.js b/src/backend/src/om/entitystorage/BaseES.js index f0658890c5..b8b879b19e 100644 --- a/src/backend/src/om/entitystorage/BaseES.js +++ b/src/backend/src/om/entitystorage/BaseES.js @@ -16,9 +16,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); -const { Context } = require("../../util/context"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); +const { Context } = require('../../util/context'); /** * BaseES is a base class for Entity Store classes. @@ -26,7 +26,7 @@ const { Context } = require("../../util/context"); class BaseES extends AdvancedBase { static FEATURES = [ new WeakConstructorFeature(), - ] + ]; // Default implementations static METHODS = { @@ -59,7 +59,7 @@ class BaseES extends AdvancedBase { throw Error('Missing terminal operation'); } return await this.upstream.create_predicate(id, ...args); - } + }, }; constructor (...a) { @@ -107,7 +107,7 @@ class BaseES extends AdvancedBase { const retry_predicate = await this.retry_predicate_rewrite(uid); if ( retry_predicate ) { entity = await this.call_on_impl_('read', - { predicate: retry_predicate }); + { predicate: retry_predicate }); } } if ( ! this.impl_methods.read_transform ) return entity; @@ -136,7 +136,6 @@ class BaseES extends AdvancedBase { return await this.call_on_impl_('retry_predicate_rewrite', { predicate }); } - async read_transform (entity) { if ( ! entity ) return entity; if ( ! this.impl_methods.read_transform ) return entity; diff --git a/src/backend/src/om/entitystorage/ESBuilder.js b/src/backend/src/om/entitystorage/ESBuilder.js index e5456261cf..3179b01481 100644 --- a/src/backend/src/om/entitystorage/ESBuilder.js +++ b/src/backend/src/om/entitystorage/ESBuilder.js @@ -23,7 +23,7 @@ class ESBuilder { const apply_next = () => { const args = []; let last_was_cons = false; - while ( ! last_was_cons ) { + while ( !last_was_cons ) { const item = stack.pop(); if ( typeof item === 'function' ) { last_was_cons = true; @@ -36,7 +36,7 @@ class ESBuilder { ...(args[0] ?? {}), ...(head ? { upstream: head } : {}), }); - } + }; for ( const item of list ) { const is_cons = typeof item === 'function'; diff --git a/src/backend/src/om/entitystorage/Entity.js b/src/backend/src/om/entitystorage/Entity.js index 8cb7c3c286..e79000ef81 100644 --- a/src/backend/src/om/entitystorage/Entity.js +++ b/src/backend/src/om/entitystorage/Entity.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); class Entity extends AdvancedBase { static FEATURES = [ new WeakConstructorFeature(), - ] + ]; constructor (args) { super(args); @@ -105,7 +105,7 @@ class Entity extends AdvancedBase { } // If value is not set but we have an implicator, use it. - if ( ! is_set && prop.descriptor.imply ) { + if ( !is_set && prop.descriptor.imply ) { const { given, make } = prop.descriptor.imply; let imply_available = true; for ( const g of given ) { @@ -117,7 +117,7 @@ class Entity extends AdvancedBase { if ( imply_available ) { value = await make(this.values_); value = await prop.adapt(value); - is_set = await prop.is_set(value); + is_set = await prop.is_set(value); } if ( is_set ) this.values_[key] = value; } diff --git a/src/backend/src/om/entitystorage/MaxLimitES.js b/src/backend/src/om/entitystorage/MaxLimitES.js index e528e87d48..4e71f5ef0a 100644 --- a/src/backend/src/om/entitystorage/MaxLimitES.js +++ b/src/backend/src/om/entitystorage/MaxLimitES.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { BaseES } = require("./BaseES"); +const { BaseES } = require('./BaseES'); class MaxLimitES extends BaseES { static METHODS = { @@ -36,7 +36,7 @@ class MaxLimitES extends BaseES { options.limit = limit; return await this.upstream.select(options); - } + }, }; } diff --git a/src/backend/src/om/entitystorage/NotificationES.js b/src/backend/src/om/entitystorage/NotificationES.js index 7c666852e2..aaa28be33b 100644 --- a/src/backend/src/om/entitystorage/NotificationES.js +++ b/src/backend/src/om/entitystorage/NotificationES.js @@ -16,9 +16,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { nou } = require("../../util/langutil"); -const { Eq, IsNotNull } = require("../query/query"); -const { BaseES } = require("./BaseES"); +const { nou } = require('../../util/langutil'); +const { Eq, IsNotNull } = require('../query/query'); +const { BaseES } = require('./BaseES'); class NotificationES extends BaseES { static METHODS = { @@ -53,8 +53,8 @@ class NotificationES extends BaseES { value = {}; } await entity.set('value', value); - } - } + }, + }; } module.exports = { NotificationES }; \ No newline at end of file diff --git a/src/backend/src/om/entitystorage/OwnerLimitedES.js b/src/backend/src/om/entitystorage/OwnerLimitedES.js index a892510135..a1125f3168 100644 --- a/src/backend/src/om/entitystorage/OwnerLimitedES.js +++ b/src/backend/src/om/entitystorage/OwnerLimitedES.js @@ -16,10 +16,10 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { UserActorType } = require("../../services/auth/Actor"); -const { Context } = require("../../util/context"); -const { Eq } = require("../query/query"); -const { BaseES } = require("./BaseES"); +const { UserActorType } = require('../../services/auth/Actor'); +const { Context } = require('../../util/context'); +const { Eq } = require('../query/query'); +const { BaseES } = require('./BaseES'); class OwnerLimitedES extends BaseES { // Limit selection to entities owned by the app of the current actor. @@ -51,7 +51,7 @@ class OwnerLimitedES extends BaseES { const entity = await this.upstream.read(uid); if ( ! entity ) return null; - + const entity_owner = await entity.get('owner'); let owner_id = entity_owner?.id; if ( entity_owner.id !== actor.type.user.id ) { @@ -65,4 +65,3 @@ class OwnerLimitedES extends BaseES { module.exports = { OwnerLimitedES, }; - diff --git a/src/backend/src/om/entitystorage/ProtectedAppES.js b/src/backend/src/om/entitystorage/ProtectedAppES.js index 6628df3308..7ac0384415 100644 --- a/src/backend/src/om/entitystorage/ProtectedAppES.js +++ b/src/backend/src/om/entitystorage/ProtectedAppES.js @@ -16,49 +16,49 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AppUnderUserActorType, UserActorType } = require("../../services/auth/Actor"); -const { PermissionUtil } = require("../../services/auth/permissionUtils.mjs"); -const { Context } = require("../../util/context"); -const { BaseES } = require("./BaseES"); +const { AppUnderUserActorType, UserActorType } = require('../../services/auth/Actor'); +const { PermissionUtil } = require('../../services/auth/permissionUtils.mjs'); +const { Context } = require('../../util/context'); +const { BaseES } = require('./BaseES'); class ProtectedAppES extends BaseES { - async select (options){ + async select (options) { const results = await this.upstream.select(options); - + const actor = Context.get('actor'); const services = Context.get('services'); - + const to_delete = []; - for ( let i=0 ; i < results.length ; i++ ) { + for ( let i = 0 ; i < results.length ; i++ ) { const entity = results[i]; - + if ( ! await this.check_({ actor, services }, entity) ) { continue; } - + to_delete.push(i); } - + const svc_utilArray = services.get('util-array'); svc_utilArray.remove_marked_items(to_delete, results); - + return results; } - - async read (uid){ + + async read (uid) { const entity = await this.upstream.read(uid); if ( ! entity ) return null; - + const actor = Context.get('actor'); const services = Context.get('services'); if ( await this.check_({ actor, services }, entity) ) { return null; } - + return entity; } - + /** * returns true if the entity should not be sent downstream */ @@ -67,31 +67,29 @@ class ProtectedAppES extends BaseES { { // if it's not a protected app, no worries if ( ! await entity.get('protected') ) return; - + // if actor is this app, no worries if ( actor.type instanceof AppUnderUserActorType && await entity.get('uid') === actor.type.app.uid ) return; - + // if actor is owner of this app, no worries if ( actor.type instanceof UserActorType && (await entity.get('owner')).id === actor.type.user.id ) return; } - + // now we need to check for permission const app_uid = await entity.get('uid'); const svc_permission = services.get('permission'); const permission_to_check = `app:uid#${app_uid}:access`; - const reading = await svc_permission.scan( - actor, permission_to_check, - ); + const reading = await svc_permission.scan(actor, permission_to_check); const options = PermissionUtil.reading_to_options(reading); - + if ( options.length > 0 ) return; - + // `true` here means "do not send downstream" return true; } diff --git a/src/backend/src/om/entitystorage/ReadOnlyES.js b/src/backend/src/om/entitystorage/ReadOnlyES.js index 5dafb4170c..952bde6651 100644 --- a/src/backend/src/om/entitystorage/ReadOnlyES.js +++ b/src/backend/src/om/entitystorage/ReadOnlyES.js @@ -16,8 +16,8 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { BaseES } = require("./BaseES"); +const APIError = require('../../api/APIError'); +const { BaseES } = require('./BaseES'); class ReadOnlyES extends BaseES { async upsert () { diff --git a/src/backend/src/om/entitystorage/SQLES.js b/src/backend/src/om/entitystorage/SQLES.js index f2ecd6018b..c0f3126ed8 100644 --- a/src/backend/src/om/entitystorage/SQLES.js +++ b/src/backend/src/om/entitystorage/SQLES.js @@ -16,20 +16,20 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { BaseES } = require("./BaseES"); +const { AdvancedBase } = require('@heyputer/putility'); +const { BaseES } = require('./BaseES'); -const APIError = require("../../api/APIError"); -const { Entity } = require("./Entity"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); -const { And, Or, Eq, Like, Null, Predicate, PredicateUtil, IsNotNull, StartsWith } = require("../query/query"); -const { DB_WRITE } = require("../../services/database/consts"); +const APIError = require('../../api/APIError'); +const { Entity } = require('./Entity'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); +const { And, Or, Eq, Like, Null, Predicate, PredicateUtil, IsNotNull, StartsWith } = require('../query/query'); +const { DB_WRITE } = require('../../services/database/consts'); class RawCondition extends AdvancedBase { // properties: sql:string, values:any[] static FEATURES = [ new WeakConstructorFeature(), - ] + ]; } class SQLES extends BaseES { @@ -56,14 +56,12 @@ class SQLES extends BaseES { if ( typeof uid === 'number' ) { id_col = 'id'; } - return [` WHERE ${id_col} = ?`, [uid]] + return [` WHERE ${id_col} = ?`, [uid]]; } - + if ( ! uid.hasOwnProperty('predicate') ) { - throw new Error( - 'SQLES.read does not understand this input: ' + - 'object with no predicate property', - ); + throw new Error('SQLES.read does not understand this input: ' + + 'object with no predicate property'); } let predicate = uid.predicate; // uid is actually a predicate if ( predicate instanceof Predicate ) { @@ -77,9 +75,7 @@ class SQLES extends BaseES { const stmt = `SELECT * FROM ${this.om.sql.table_name}${stmt_where}`; - const rows = await this.db.read( - stmt, where_vals - ); + const rows = await this.db.read(stmt, where_vals); if ( rows.length === 0 ) { return null; @@ -143,7 +139,7 @@ class SQLES extends BaseES { values.push(value); if ( old_entity ) { - stmt += ` AND id != ?`; + stmt += ' AND id != ?'; values.push(old_entity.private_meta.mysql_id); } @@ -176,9 +172,7 @@ class SQLES extends BaseES { const stmt = `DELETE FROM ${this.om.sql.table_name} WHERE ${id_col} = ?`; - const res = await this.db.write( - stmt, [uid] - ); + const res = await this.db.write(stmt, [uid]); if ( ! res.anyRowsAffected ) { throw APIError.create('entity_not_found', null, { @@ -253,9 +247,7 @@ class SQLES extends BaseES { // console.log('SQL STMT', stmt); // console.log('SQL VALS', execute_vals); - const res = await this.db.write( - stmt, execute_vals - ); + const res = await this.db.write(stmt, execute_vals); return { data: sql_data, @@ -286,9 +278,7 @@ class SQLES extends BaseES { // console.log('SQL STMT', stmt); // console.log('SQL VALS', execute_vals); - await this.db.write( - stmt, execute_vals - ); + await this.db.write(stmt, execute_vals); const full_entity = await (await old_entity.clone()).apply(entity); @@ -319,7 +309,7 @@ class SQLES extends BaseES { } value = await prop.sql_reference(value); - + // TODO: This is done here for consistency; // see the larger comment in sql_row_to_entity_ // which does the reverse operation. @@ -395,8 +385,8 @@ class SQLES extends BaseES { return new RawCondition({ sql, values }); } - - if (om_query instanceof StartsWith) { + + if ( om_query instanceof StartsWith ) { const key = om_query.key; let value = om_query.value; const prop = this.om.properties[key]; @@ -407,8 +397,8 @@ class SQLES extends BaseES { const col_name = options.column_name ?? prop.name; const sql = `${col_name} LIKE ${this.db.case({ - sqlite: `? || '%'`, - otherwise: `CONCAT(?, '%')` + sqlite: '? || \'%\'', + otherwise: 'CONCAT(?, \'%\')', })}`; const values = value === null ? [] : [value]; @@ -446,8 +436,8 @@ class SQLES extends BaseES { return new RawCondition({ sql, values }); } - } - } + }, + }; } module.exports = SQLES; diff --git a/src/backend/src/om/entitystorage/SetOwnerES.js b/src/backend/src/om/entitystorage/SetOwnerES.js index 86cefae5aa..d7cd31d74d 100644 --- a/src/backend/src/om/entitystorage/SetOwnerES.js +++ b/src/backend/src/om/entitystorage/SetOwnerES.js @@ -16,11 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { get_user } = require("../../helpers"); -const { AppUnderUserActorType, UserActorType } = require("../../services/auth/Actor"); -const { Context } = require("../../util/context"); -const { nou } = require("../../util/langutil"); -const { BaseES } = require("./BaseES"); +const { get_user } = require('../../helpers'); +const { AppUnderUserActorType, UserActorType } = require('../../services/auth/Actor'); +const { Context } = require('../../util/context'); +const { nou } = require('../../util/langutil'); +const { BaseES } = require('./BaseES'); class SetOwnerES extends BaseES { static METHODS = { @@ -68,7 +68,7 @@ class SetOwnerES extends BaseES { if ( nou(owner) ) return null; owner = get_user({ id: owner }); await entity.set('owner', owner); - } + }, }; } diff --git a/src/backend/src/om/entitystorage/SubdomainES.js b/src/backend/src/om/entitystorage/SubdomainES.js index 45cc9473fb..06798b9458 100644 --- a/src/backend/src/om/entitystorage/SubdomainES.js +++ b/src/backend/src/om/entitystorage/SubdomainES.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const config = require("../../config"); +const APIError = require('../../api/APIError'); +const config = require('../../config'); -const { DB_READ } = require("../../services/database/consts"); -const { Context } = require("../../util/context"); -const { Eq } = require("../query/query"); -const { BaseES } = require("./BaseES"); +const { DB_READ } = require('../../services/database/consts'); +const { Context } = require('../../util/context'); +const { Eq } = require('../query/query'); +const { BaseES } = require('./BaseES'); const PERM_READ_ALL_SUBDOMAINS = 'read-all-subdomains'; @@ -55,15 +55,13 @@ class SubdomainES extends BaseES { // non-owner users don't have permission to list // but they still have permission to read. const svc_permission = this.context.get('services').get('permission'); - const has_permission_to_read_all = await svc_permission.check(Context.get("actor"), PERM_READ_ALL_SUBDOMAINS); + const has_permission_to_read_all = await svc_permission.check(Context.get('actor'), PERM_READ_ALL_SUBDOMAINS); - if (!has_permission_to_read_all) { - options.predicate = options.predicate.and( - new Eq({ - key: 'owner', - value: user.id, - }), - ); + if ( ! has_permission_to_read_all ) { + options.predicate = options.predicate.and(new Eq({ + key: 'owner', + value: user.id, + })); } return await this.upstream.select(options); @@ -71,10 +69,8 @@ class SubdomainES extends BaseES { async _check_max_subdomains () { const user = Context.get('user'); - let cnt = await this.db.read( - `SELECT COUNT(id) AS subdomain_count FROM subdomains WHERE user_id = ?`, - [user.id], - ); + let cnt = await this.db.read('SELECT COUNT(id) AS subdomain_count FROM subdomains WHERE user_id = ?', + [user.id]); const max_subdomains = user.max_subdomains ?? config.max_subdomains_per_user; @@ -83,8 +79,8 @@ class SubdomainES extends BaseES { limit: max_subdomains, }); } - } - } + }, + }; } module.exports = SubdomainES; \ No newline at end of file diff --git a/src/backend/src/om/entitystorage/ValidationES.js b/src/backend/src/om/entitystorage/ValidationES.js index 2490e42244..e3f236dfbb 100644 --- a/src/backend/src/om/entitystorage/ValidationES.js +++ b/src/backend/src/om/entitystorage/ValidationES.js @@ -16,11 +16,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { BaseES } = require("./BaseES"); +const { BaseES } = require('./BaseES'); -const APIError = require("../../api/APIError"); -const { Context } = require("../../util/context"); -const { SKIP_ES_VALIDATION } = require("./consts"); +const APIError = require('../../api/APIError'); +const { Context } = require('../../util/context'); +const { SKIP_ES_VALIDATION } = require('./consts'); class ValidationES extends BaseES { async _on_context_provided () { @@ -52,10 +52,8 @@ class ValidationES extends BaseES { ? await (await extra.old_entity.clone()).apply(entity) : entity ; - await this.validate_( - valid_entity, - extra.old_entity ? entity : undefined - ); + await this.validate_(valid_entity, + extra.old_entity ? entity : undefined); const { entity: out_entity } = await this.upstream.upsert(entity, extra); return await out_entity.get_client_safe(); }, @@ -100,4 +98,3 @@ class ValidationES extends BaseES { } module.exports = ValidationES; - diff --git a/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js b/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js index 200b7436a2..6fa434ea98 100644 --- a/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js +++ b/src/backend/src/om/entitystorage/WriteByOwnerOnlyES.js @@ -16,9 +16,9 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { Context } = require("../../util/context"); -const { BaseES } = require("./BaseES"); +const APIError = require('../../api/APIError'); +const { Context } = require('../../util/context'); +const { BaseES } = require('./BaseES'); const WRITE_ALL_OWNER_ES = 'system:es:write-all-owners'; @@ -70,11 +70,11 @@ class WriteByOwnerOnlyES extends BaseES { */ async _check_allowed ({ old_entity }) { const svc_permission = this.context.get('services').get('permission'); - const has_permission_to_write_all = await svc_permission.check(Context.get("actor"), WRITE_ALL_OWNER_ES); - if (has_permission_to_write_all) { + const has_permission_to_write_all = await svc_permission.check(Context.get('actor'), WRITE_ALL_OWNER_ES); + if ( has_permission_to_write_all ) { return; } - + const owner = await old_entity.get('owner'); if ( ! owner ) { throw APIError.create('forbidden'); @@ -84,9 +84,9 @@ class WriteByOwnerOnlyES extends BaseES { if ( user.id !== owner.id ) { throw APIError.create('forbidden'); } - } + }, - } + }; } module.exports = WriteByOwnerOnlyES; diff --git a/src/backend/src/om/mappings/access-token.js b/src/backend/src/om/mappings/access-token.js index 06ae13fb8a..28a9637ae1 100644 --- a/src/backend/src/om/mappings/access-token.js +++ b/src/backend/src/om/mappings/access-token.js @@ -18,7 +18,7 @@ */ module.exports = { sql: { - table_name: 'access_token_permissions' + table_name: 'access_token_permissions', }, primary_identifier: 'token', }; \ No newline at end of file diff --git a/src/backend/src/om/mappings/app.js b/src/backend/src/om/mappings/app.js index 9d67490ca2..ab621ce40a 100644 --- a/src/backend/src/om/mappings/app.js +++ b/src/backend/src/om/mappings/app.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const config = require("../../config"); +const config = require('../../config'); module.exports = { sql: { @@ -58,7 +58,7 @@ module.exports = { subdomain: { type: 'string', transient: true, - factory: () => 'app-' + require('uuid').v4(), + factory: () => `app-${ require('uuid').v4()}`, sql: { ignore: true }, }, index_url: { @@ -68,8 +68,8 @@ module.exports = { imply: { given: ['subdomain', 'source_directory'], make: async ({ subdomain }) => { - return config.protocol + '://' + subdomain + '.puter.site'; - } + return `${config.protocol }://${ subdomain }.puter.site`; + }, }, }, source_directory: { @@ -82,22 +82,23 @@ module.exports = { aliases: ['timestamp'], sql: { column_name: 'timestamp', - } + }, }, filetype_associations: { - type: 'array', of: 'string', - sql: { ignore: true } + type: 'array', + of: 'string', + sql: { ignore: true }, }, // DOMAIN :: CALCULATED stats: { type: 'json', - sql: { ignore: true } + sql: { ignore: true }, }, created_from_origin: { type: 'string', - sql: { ignore: true } + sql: { ignore: true }, }, // ACCESS @@ -109,7 +110,7 @@ module.exports = { sql: { use_id: true, column_name: 'owner_user_id', - } + }, }, app_owner: { type: 'reference', @@ -144,5 +145,5 @@ module.exports = { type: 'flag', read_only: true, }, - } -} + }, +}; diff --git a/src/backend/src/om/mappings/notification.js b/src/backend/src/om/mappings/notification.js index 5f695f5cde..c204d6ed9a 100644 --- a/src/backend/src/om/mappings/notification.js +++ b/src/backend/src/om/mappings/notification.js @@ -18,7 +18,7 @@ */ module.exports = { sql: { - table_name: 'notification' + table_name: 'notification', }, primary_identifier: 'uid', properties: { @@ -33,7 +33,7 @@ module.exports = { sql: { use_id: true, column_name: 'user_id', - } - } - } + }, + }, + }, }; diff --git a/src/backend/src/om/mappings/subdomain.js b/src/backend/src/om/mappings/subdomain.js index 80dc8cb30e..07bd34ce42 100644 --- a/src/backend/src/om/mappings/subdomain.js +++ b/src/backend/src/om/mappings/subdomain.js @@ -51,7 +51,7 @@ module.exports = { subdomain: value, }); } - } + }, }, domain: { type: 'string', @@ -63,8 +63,10 @@ module.exports = { // TODO: can this 'adapt' be data instead? async adapt (value) { - if (value !== null) + if ( value !== null ) + { return value.toLowerCase(); + } return null; }, }, @@ -73,7 +75,7 @@ module.exports = { fs_permission: 'read', sql: { column_name: 'root_dir_id', - } + }, }, associated_app: { type: 'reference', @@ -82,7 +84,7 @@ module.exports = { sql: { use_id: true, column_name: 'associated_app_id', - } + }, }, created_at: { type: 'datetime', @@ -112,6 +114,5 @@ module.exports = { protected: { type: 'flag', }, - } + }, }; - diff --git a/src/backend/src/om/proptypes/__all__.js b/src/backend/src/om/proptypes/__all__.js index 830b71fed8..45212a06e3 100644 --- a/src/backend/src/om/proptypes/__all__.js +++ b/src/backend/src/om/proptypes/__all__.js @@ -16,15 +16,15 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const APIError = require("../../api/APIError"); -const { NodeUIDSelector, NodeInternalIDSelector, NodePathSelector } = require("../../filesystem/node/selectors"); -const { is_valid_uuid4, is_valid_uuid } = require("../../helpers"); -const validator = require("validator"); -const { Context } = require("../../util/context"); -const { is_valid_path } = require("../../filesystem/validation"); -const FSNodeContext = require("../../filesystem/FSNodeContext"); -const { Entity } = require("../entitystorage/Entity"); -const NULL = Symbol("NULL") +const APIError = require('../../api/APIError'); +const { NodeUIDSelector, NodeInternalIDSelector, NodePathSelector } = require('../../filesystem/node/selectors'); +const { is_valid_uuid4, is_valid_uuid } = require('../../helpers'); +const validator = require('validator'); +const { Context } = require('../../util/context'); +const { is_valid_path } = require('../../filesystem/validation'); +const FSNodeContext = require('../../filesystem/FSNodeContext'); +const { Entity } = require('../entitystorage/Entity'); +const NULL = Symbol('NULL'); class OMTypeError extends Error { constructor ({ expected, got }) { @@ -37,7 +37,7 @@ class OMTypeError extends Error { module.exports = { base: { is_set (value) { - return !! value; + return !!value; }, }, json: { @@ -45,7 +45,7 @@ module.exports = { }, string: { is_set (value) { - return (!!value) || value === null + return (!!value) || value === null; }, async adapt (value) { if ( value === undefined ) return ''; @@ -54,7 +54,7 @@ module.exports = { // then this should become an sql-to-entity adapt only. if ( value === null ) return ''; - if (value === NULL) { + if ( value === NULL ) { return null; } @@ -73,11 +73,11 @@ module.exports = { if ( descriptor.hasOwnProperty('minlen') && value.length > descriptor.minlen ) { throw APIError.create('field_too_short', null, { key: name, min_length: descriptor.maxlen }); } - if ( descriptor.hasOwnProperty('regex') && ! value.match(descriptor.regex) ) { + if ( descriptor.hasOwnProperty('regex') && !value.match(descriptor.regex) ) { return new Error(`string does not match regex ${descriptor.regex}`); } return true; - } + }, }, array: { from: 'base', @@ -95,7 +95,7 @@ module.exports = { throw APIError.create('field_invalid', null, { key: name, mod: descriptor.mod }); } return true; - } + }, }, flag: { adapt: value => { @@ -108,7 +108,7 @@ module.exports = { throw new OMTypeError({ expected: 'boolean', got: typeof value }); } return value; - } + }, }, uuid: { from: 'string', @@ -119,14 +119,14 @@ module.exports = { ['puter-uuid']: { from: 'string', validate (value, { descriptor }) { - const prefix = descriptor.prefix + '-'; + const prefix = `${descriptor.prefix }-`; if ( ! value.startsWith(prefix) ) { return new Error(`UUID does not start with prefix ${prefix}`); } return is_valid_uuid(value.slice(prefix.length)); }, factory ({ descriptor }) { - const prefix = descriptor.prefix + '-'; + const prefix = `${descriptor.prefix }-`; const uuid = require('uuid').v4(); return prefix + uuid; }, @@ -142,7 +142,7 @@ module.exports = { if ( chars.some(char => value.includes(char)) ) { return new Error('icon is not an image'); } - } + }, }, url: { from: 'string', @@ -152,7 +152,7 @@ module.exports = { valid = validator.isURL(value, { host_whitelist: ['localhost'] }); } return valid; - } + }, }, reference: { from: 'base', @@ -181,7 +181,7 @@ module.exports = { const svc = Context.get().get('services').get(descriptor.service); const entity = await svc.read(value); return entity; - } + }, }, datetime: { from: 'base', @@ -197,19 +197,15 @@ module.exports = { return value.mysql_id ?? null; }, async is_set (value) { - return ( !! value ) || value === null; + return ( !!value ) || value === null; }, async sql_dereference (value) { if ( value === null ) return null; if ( typeof value !== 'number' ) { - throw new Error( - `Cannot dereference non-number: ${value}` - ); + throw new Error(`Cannot dereference non-number: ${value}`); } const svc_fs = Context.get().get('services').get('filesystem'); - return svc_fs.node( - new NodeInternalIDSelector('mysql', value) - ); + return svc_fs.node(new NodeInternalIDSelector('mysql', value)); }, async adapt (value, { name }) { if ( value === null ) return null; @@ -222,7 +218,7 @@ module.exports = { if ( typeof value !== 'string' ) return; let selector; - if ( ! ['/','.','~'].includes(value[0]) ) { + if ( ! ['/', '.', '~'].includes(value[0]) ) { if ( is_valid_uuid4(value) ) { selector = new NodeUIDSelector(value); } @@ -262,7 +258,7 @@ module.exports = { if ( ! await svc_acl.check(actor, value, permission) ) { return await svc_acl.get_safe_acl_error(actor, value, permission); } - } + }, }, - NULL + NULL, }; diff --git a/src/backend/src/om/query/query.js b/src/backend/src/om/query/query.js index 96d2ea666c..dbc7b13b4b 100644 --- a/src/backend/src/om/query/query.js +++ b/src/backend/src/om/query/query.js @@ -16,13 +16,13 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -const { AdvancedBase } = require("@heyputer/putility"); -const { WeakConstructorFeature } = require("../../traits/WeakConstructorFeature"); +const { AdvancedBase } = require('@heyputer/putility'); +const { WeakConstructorFeature } = require('../../traits/WeakConstructorFeature'); class Predicate extends AdvancedBase { static FEATURES = [ new WeakConstructorFeature(), - ] + ]; } class Null extends Predicate { @@ -51,7 +51,7 @@ class Eq extends Predicate { } class StartsWith extends Predicate { - async check(entity) { + async check (entity) { return (await entity.get(this.key)).startsWith(this.value); } } @@ -73,7 +73,7 @@ class Like extends Predicate { Predicate.prototype.and = function (other) { return new And({ children: [this, other] }); -} +}; class PredicateUtil { static simplify (predicate) { @@ -128,5 +128,5 @@ module.exports = { Eq, IsNotNull, Like, - StartsWith + StartsWith, }; diff --git a/src/backend/src/polyfill/to-string-higher-radix.js b/src/backend/src/polyfill/to-string-higher-radix.js index a613c3ccba..6144c1fdc8 100644 --- a/src/backend/src/polyfill/to-string-higher-radix.js +++ b/src/backend/src/polyfill/to-string-higher-radix.js @@ -20,22 +20,22 @@ * Polyfill written by Chat GPT that increases the highest suppored * radix on Number.prototype.toString from 36 to 62. */ -(function() { +(function () { const originalToString = Number.prototype.toString; - const characters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; const base = characters.length; // 62 - Number.prototype.toString = function(radix) { + Number.prototype.toString = function (radix) { // Use the original toString for bases 36 or lower - if (!radix || radix <= 36) { + if ( !radix || radix <= 36 ) { return originalToString.call(this, radix); } // Custom implementation for base 62 let value = this; let result = ''; - while (value > 0) { + while ( value > 0 ) { result = characters[value % base] + result; value = Math.floor(value / base); } diff --git a/src/backend/src/public/assets/bootstrap-5.1.3/js/bootstrap.bundle.js b/src/backend/src/public/assets/bootstrap-5.1.3/js/bootstrap.bundle.js index e5d26a25ce..49460d4441 100644 --- a/src/backend/src/public/assets/bootstrap-5.1.3/js/bootstrap.bundle.js +++ b/src/backend/src/public/assets/bootstrap-5.1.3/js/bootstrap.bundle.js @@ -4,195 +4,191 @@ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) */ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory()); -})(this, (function () { 'use strict'; + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.bootstrap = factory()); +})(this, (function () { + 'use strict'; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/index.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const MAX_UID = 1000000; - const MILLISECONDS_MULTIPLIER = 1000; - const TRANSITION_END = 'transitionend'; // Shoutout AngusCroll (https://goo.gl/pxwQGp) + const MAX_UID = 1000000; + const MILLISECONDS_MULTIPLIER = 1000; + const TRANSITION_END = 'transitionend'; // Shoutout AngusCroll (https://goo.gl/pxwQGp) - const toType = obj => { - if (obj === null || obj === undefined) { - return `${obj}`; - } + const toType = obj => { + if ( obj === null || obj === undefined ) { + return `${obj}`; + } - return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase(); - }; - /** + return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase(); + }; + /** * -------------------------------------------------------------------------- * Public Util Api * -------------------------------------------------------------------------- */ + const getUID = prefix => { + do { + prefix += Math.floor(Math.random() * MAX_UID); + } while ( document.getElementById(prefix) ); - const getUID = prefix => { - do { - prefix += Math.floor(Math.random() * MAX_UID); - } while (document.getElementById(prefix)); - - return prefix; - }; - - const getSelector = element => { - let selector = element.getAttribute('data-bs-target'); - - if (!selector || selector === '#') { - let hrefAttr = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes, - // so everything starting with `#` or `.`. If a "real" URL is used as the selector, - // `document.querySelector` will rightfully complain it is invalid. - // See https://github.com/twbs/bootstrap/issues/32273 - - if (!hrefAttr || !hrefAttr.includes('#') && !hrefAttr.startsWith('.')) { - return null; - } // Just in case some CMS puts out a full URL with the anchor appended - - - if (hrefAttr.includes('#') && !hrefAttr.startsWith('#')) { - hrefAttr = `#${hrefAttr.split('#')[1]}`; - } - - selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null; - } - - return selector; - }; - - const getSelectorFromElement = element => { - const selector = getSelector(element); + return prefix; + }; - if (selector) { - return document.querySelector(selector) ? selector : null; - } + const getSelector = element => { + let selector = element.getAttribute('data-bs-target'); - return null; - }; + if ( !selector || selector === '#' ) { + let hrefAttr = element.getAttribute('href'); // The only valid content that could double as a selector are IDs or classes, + // so everything starting with `#` or `.`. If a "real" URL is used as the selector, + // `document.querySelector` will rightfully complain it is invalid. + // See https://github.com/twbs/bootstrap/issues/32273 - const getElementFromSelector = element => { - const selector = getSelector(element); - return selector ? document.querySelector(selector) : null; - }; + if ( !hrefAttr || !hrefAttr.includes('#') && !hrefAttr.startsWith('.') ) { + return null; + } // Just in case some CMS puts out a full URL with the anchor appended - const getTransitionDurationFromElement = element => { - if (!element) { - return 0; - } // Get transition-duration of the element + if ( hrefAttr.includes('#') && !hrefAttr.startsWith('#') ) { + hrefAttr = `#${hrefAttr.split('#')[1]}`; + } + selector = hrefAttr && hrefAttr !== '#' ? hrefAttr.trim() : null; + } - let { - transitionDuration, - transitionDelay - } = window.getComputedStyle(element); - const floatTransitionDuration = Number.parseFloat(transitionDuration); - const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found + return selector; + }; - if (!floatTransitionDuration && !floatTransitionDelay) { - return 0; - } // If multiple durations are defined, take the first + const getSelectorFromElement = element => { + const selector = getSelector(element); + if ( selector ) { + return document.querySelector(selector) ? selector : null; + } - transitionDuration = transitionDuration.split(',')[0]; - transitionDelay = transitionDelay.split(',')[0]; - return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; - }; + return null; + }; - const triggerTransitionEnd = element => { - element.dispatchEvent(new Event(TRANSITION_END)); - }; + const getElementFromSelector = element => { + const selector = getSelector(element); + return selector ? document.querySelector(selector) : null; + }; - const isElement$1 = obj => { - if (!obj || typeof obj !== 'object') { - return false; - } + const getTransitionDurationFromElement = element => { + if ( ! element ) { + return 0; + } // Get transition-duration of the element + + let { + transitionDuration, + transitionDelay, + } = window.getComputedStyle(element); + const floatTransitionDuration = Number.parseFloat(transitionDuration); + const floatTransitionDelay = Number.parseFloat(transitionDelay); // Return 0 if element or transition duration is not found + + if ( !floatTransitionDuration && !floatTransitionDelay ) { + return 0; + } // If multiple durations are defined, take the first + + transitionDuration = transitionDuration.split(',')[0]; + transitionDelay = transitionDelay.split(',')[0]; + return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * MILLISECONDS_MULTIPLIER; + }; - if (typeof obj.jquery !== 'undefined') { - obj = obj[0]; - } + const triggerTransitionEnd = element => { + element.dispatchEvent(new Event(TRANSITION_END)); + }; - return typeof obj.nodeType !== 'undefined'; - }; + const isElement$1 = obj => { + if ( !obj || typeof obj !== 'object' ) { + return false; + } - const getElement = obj => { - if (isElement$1(obj)) { - // it's a jQuery object or a node element - return obj.jquery ? obj[0] : obj; - } + if ( typeof obj.jquery !== 'undefined' ) { + obj = obj[0]; + } - if (typeof obj === 'string' && obj.length > 0) { - return document.querySelector(obj); - } + return typeof obj.nodeType !== 'undefined'; + }; - return null; - }; + const getElement = obj => { + if ( isElement$1(obj) ) { + // it's a jQuery object or a node element + return obj.jquery ? obj[0] : obj; + } - const typeCheckConfig = (componentName, config, configTypes) => { - Object.keys(configTypes).forEach(property => { - const expectedTypes = configTypes[property]; - const value = config[property]; - const valueType = value && isElement$1(value) ? 'element' : toType(value); + if ( typeof obj === 'string' && obj.length > 0 ) { + return document.querySelector(obj); + } - if (!new RegExp(expectedTypes).test(valueType)) { - throw new TypeError(`${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); - } - }); - }; + return null; + }; - const isVisible = element => { - if (!isElement$1(element) || element.getClientRects().length === 0) { - return false; - } + const typeCheckConfig = (componentName, config, configTypes) => { + Object.keys(configTypes).forEach(property => { + const expectedTypes = configTypes[property]; + const value = config[property]; + const valueType = value && isElement$1(value) ? 'element' : toType(value); - return getComputedStyle(element).getPropertyValue('visibility') === 'visible'; - }; + if ( ! new RegExp(expectedTypes).test(valueType) ) { + throw new TypeError(`${componentName.toUpperCase()}: Option "${property}" provided type "${valueType}" but expected type "${expectedTypes}".`); + } + }); + }; - const isDisabled = element => { - if (!element || element.nodeType !== Node.ELEMENT_NODE) { - return true; - } + const isVisible = element => { + if ( !isElement$1(element) || element.getClientRects().length === 0 ) { + return false; + } - if (element.classList.contains('disabled')) { - return true; - } + return getComputedStyle(element).getPropertyValue('visibility') === 'visible'; + }; - if (typeof element.disabled !== 'undefined') { - return element.disabled; - } + const isDisabled = element => { + if ( !element || element.nodeType !== Node.ELEMENT_NODE ) { + return true; + } - return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; - }; + if ( element.classList.contains('disabled') ) { + return true; + } - const findShadowRoot = element => { - if (!document.documentElement.attachShadow) { - return null; - } // Can find the shadow root otherwise it'll return the document + if ( typeof element.disabled !== 'undefined' ) { + return element.disabled; + } + return element.hasAttribute('disabled') && element.getAttribute('disabled') !== 'false'; + }; - if (typeof element.getRootNode === 'function') { - const root = element.getRootNode(); - return root instanceof ShadowRoot ? root : null; - } + const findShadowRoot = element => { + if ( ! document.documentElement.attachShadow ) { + return null; + } // Can find the shadow root otherwise it'll return the document - if (element instanceof ShadowRoot) { - return element; - } // when we don't find a shadow root + if ( typeof element.getRootNode === 'function' ) { + const root = element.getRootNode(); + return root instanceof ShadowRoot ? root : null; + } + if ( element instanceof ShadowRoot ) { + return element; + } // when we don't find a shadow root - if (!element.parentNode) { - return null; - } + if ( ! element.parentNode ) { + return null; + } - return findShadowRoot(element.parentNode); - }; + return findShadowRoot(element.parentNode); + }; - const noop = () => {}; - /** + const noop = () => { + }; + /** * Trick to restart an element's animation * * @param {HTMLElement} element @@ -201,98 +197,97 @@ * @see https://www.charistheo.io/blog/2021/02/restart-a-css-animation-with-javascript/#restarting-a-css-animation */ + const reflow = element => { + + element.offsetHeight; + }; - const reflow = element => { - // eslint-disable-next-line no-unused-expressions - element.offsetHeight; - }; + const getjQuery = () => { + const { + jQuery, + } = window; - const getjQuery = () => { - const { - jQuery - } = window; + if ( jQuery && !document.body.hasAttribute('data-bs-no-jquery') ) { + return jQuery; + } - if (jQuery && !document.body.hasAttribute('data-bs-no-jquery')) { - return jQuery; - } + return null; + }; - return null; - }; + const DOMContentLoadedCallbacks = []; - const DOMContentLoadedCallbacks = []; + const onDOMContentLoaded = callback => { + if ( document.readyState === 'loading' ) { + // add listener on the first call when the document is in loading state + if ( ! DOMContentLoadedCallbacks.length ) { + document.addEventListener('DOMContentLoaded', () => { + DOMContentLoadedCallbacks.forEach(callback => callback()); + }); + } - const onDOMContentLoaded = callback => { - if (document.readyState === 'loading') { - // add listener on the first call when the document is in loading state - if (!DOMContentLoadedCallbacks.length) { - document.addEventListener('DOMContentLoaded', () => { - DOMContentLoadedCallbacks.forEach(callback => callback()); - }); - } + DOMContentLoadedCallbacks.push(callback); + } else { + callback(); + } + }; - DOMContentLoadedCallbacks.push(callback); - } else { - callback(); - } - }; + const isRTL = () => document.documentElement.dir === 'rtl'; - const isRTL = () => document.documentElement.dir === 'rtl'; + const defineJQueryPlugin = plugin => { + onDOMContentLoaded(() => { + const $ = getjQuery(); + /* istanbul ignore if */ - const defineJQueryPlugin = plugin => { - onDOMContentLoaded(() => { - const $ = getjQuery(); - /* istanbul ignore if */ + if ($) { + const name = plugin.NAME; + const JQUERY_NO_CONFLICT = $.fn[name]; + $.fn[name] = plugin.jQueryInterface; + $.fn[name].Constructor = plugin; - if ($) { - const name = plugin.NAME; - const JQUERY_NO_CONFLICT = $.fn[name]; - $.fn[name] = plugin.jQueryInterface; - $.fn[name].Constructor = plugin; + $.fn[name].noConflict = () => { + $.fn[name] = JQUERY_NO_CONFLICT; + return plugin.jQueryInterface; + }; + } + }); + }; - $.fn[name].noConflict = () => { - $.fn[name] = JQUERY_NO_CONFLICT; - return plugin.jQueryInterface; - }; - } - }); - }; + const execute = callback => { + if ( typeof callback === 'function' ) { + callback(); + } + }; - const execute = callback => { - if (typeof callback === 'function') { - callback(); - } - }; + const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { + if ( ! waitForTransition ) { + execute(callback); + return; + } - const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => { - if (!waitForTransition) { - execute(callback); - return; - } + const durationPadding = 5; + const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; + let called = false; - const durationPadding = 5; - const emulatedDuration = getTransitionDurationFromElement(transitionElement) + durationPadding; - let called = false; + const handler = ({ + target, + }) => { + if ( target !== transitionElement ) { + return; + } - const handler = ({ - target - }) => { - if (target !== transitionElement) { - return; - } + called = true; + transitionElement.removeEventListener(TRANSITION_END, handler); + execute(callback); + }; - called = true; - transitionElement.removeEventListener(TRANSITION_END, handler); - execute(callback); + transitionElement.addEventListener(TRANSITION_END, handler); + setTimeout(() => { + if ( ! called ) { + triggerTransitionEnd(transitionElement); + } + }, emulatedDuration); }; - - transitionElement.addEventListener(TRANSITION_END, handler); - setTimeout(() => { - if (!called) { - triggerTransitionEnd(transitionElement); - } - }, emulatedDuration); - }; - /** + /** * Return the previous/next element of a list. * * @param {array} list The list of elements @@ -302,2298 +297,2266 @@ * @return {Element|elem} The proper element */ + const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { + let index = list.indexOf(activeElement); // if the element does not exist in the list return an element depending on the direction and if cycle is allowed - const getNextActiveElement = (list, activeElement, shouldGetNext, isCycleAllowed) => { - let index = list.indexOf(activeElement); // if the element does not exist in the list return an element depending on the direction and if cycle is allowed - - if (index === -1) { - return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]; - } + if ( index === -1 ) { + return list[!shouldGetNext && isCycleAllowed ? list.length - 1 : 0]; + } - const listLength = list.length; - index += shouldGetNext ? 1 : -1; + const listLength = list.length; + index += shouldGetNext ? 1 : -1; - if (isCycleAllowed) { - index = (index + listLength) % listLength; - } + if ( isCycleAllowed ) { + index = (index + listLength) % listLength; + } - return list[Math.max(0, Math.min(index, listLength - 1))]; - }; + return list[Math.max(0, Math.min(index, listLength - 1))]; + }; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): dom/event-handler.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const namespaceRegex = /[^.]*(?=\..*)\.|.*/; - const stripNameRegex = /\..*/; - const stripUidRegex = /::\d+$/; - const eventRegistry = {}; // Events storage - - let uidEvent = 1; - const customEvents = { - mouseenter: 'mouseover', - mouseleave: 'mouseout' - }; - const customEventsRegex = /^(mouseenter|mouseleave)/i; - const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']); - /** + const namespaceRegex = /[^.]*(?=\..*)\.|.*/; + const stripNameRegex = /\..*/; + const stripUidRegex = /::\d+$/; + const eventRegistry = {}; // Events storage + + let uidEvent = 1; + const customEvents = { + mouseenter: 'mouseover', + mouseleave: 'mouseout', + }; + const customEventsRegex = /^(mouseenter|mouseleave)/i; + const nativeEvents = new Set(['click', 'dblclick', 'mouseup', 'mousedown', 'contextmenu', 'mousewheel', 'DOMMouseScroll', 'mouseover', 'mouseout', 'mousemove', 'selectstart', 'selectend', 'keydown', 'keypress', 'keyup', 'orientationchange', 'touchstart', 'touchmove', 'touchend', 'touchcancel', 'pointerdown', 'pointermove', 'pointerup', 'pointerleave', 'pointercancel', 'gesturestart', 'gesturechange', 'gestureend', 'focus', 'blur', 'change', 'reset', 'select', 'submit', 'focusin', 'focusout', 'load', 'unload', 'beforeunload', 'resize', 'move', 'DOMContentLoaded', 'readystatechange', 'error', 'abort', 'scroll']); + /** * ------------------------------------------------------------------------ * Private methods * ------------------------------------------------------------------------ */ - function getUidEvent(element, uid) { - return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++; - } + function getUidEvent (element, uid) { + return uid && `${uid}::${uidEvent++}` || element.uidEvent || uidEvent++; + } - function getEvent(element) { - const uid = getUidEvent(element); - element.uidEvent = uid; - eventRegistry[uid] = eventRegistry[uid] || {}; - return eventRegistry[uid]; - } + function getEvent (element) { + const uid = getUidEvent(element); + element.uidEvent = uid; + eventRegistry[uid] = eventRegistry[uid] || {}; + return eventRegistry[uid]; + } - function bootstrapHandler(element, fn) { - return function handler(event) { - event.delegateTarget = element; + function bootstrapHandler (element, fn) { + return function handler (event) { + event.delegateTarget = element; - if (handler.oneOff) { - EventHandler.off(element, event.type, fn); - } + if ( handler.oneOff ) { + EventHandler.off(element, event.type, fn); + } - return fn.apply(element, [event]); - }; - } + return fn.apply(element, [event]); + }; + } - function bootstrapDelegationHandler(element, selector, fn) { - return function handler(event) { - const domElements = element.querySelectorAll(selector); + function bootstrapDelegationHandler (element, selector, fn) { + return function handler (event) { + const domElements = element.querySelectorAll(selector); - for (let { - target - } = event; target && target !== this; target = target.parentNode) { - for (let i = domElements.length; i--;) { - if (domElements[i] === target) { - event.delegateTarget = target; + for ( let { + target, + } = event; target && target !== this; target = target.parentNode ) { + for ( let i = domElements.length; i--; ) { + if ( domElements[i] === target ) { + event.delegateTarget = target; - if (handler.oneOff) { - EventHandler.off(element, event.type, selector, fn); - } + if ( handler.oneOff ) { + EventHandler.off(element, event.type, selector, fn); + } - return fn.apply(target, [event]); - } - } - } // To please ESLint + return fn.apply(target, [event]); + } + } + } // To please ESLint + return null; + }; + } - return null; - }; - } + function findHandler (events, handler, delegationSelector = null) { + const uidEventList = Object.keys(events); - function findHandler(events, handler, delegationSelector = null) { - const uidEventList = Object.keys(events); + for ( let i = 0, len = uidEventList.length; i < len; i++ ) { + const event = events[uidEventList[i]]; - for (let i = 0, len = uidEventList.length; i < len; i++) { - const event = events[uidEventList[i]]; + if ( event.originalHandler === handler && event.delegationSelector === delegationSelector ) { + return event; + } + } - if (event.originalHandler === handler && event.delegationSelector === delegationSelector) { - return event; - } + return null; } - return null; - } + function normalizeParams (originalTypeEvent, handler, delegationFn) { + const delegation = typeof handler === 'string'; + const originalHandler = delegation ? delegationFn : handler; + let typeEvent = getTypeEvent(originalTypeEvent); + const isNative = nativeEvents.has(typeEvent); - function normalizeParams(originalTypeEvent, handler, delegationFn) { - const delegation = typeof handler === 'string'; - const originalHandler = delegation ? delegationFn : handler; - let typeEvent = getTypeEvent(originalTypeEvent); - const isNative = nativeEvents.has(typeEvent); + if ( ! isNative ) { + typeEvent = originalTypeEvent; + } - if (!isNative) { - typeEvent = originalTypeEvent; + return [delegation, originalHandler, typeEvent]; } - return [delegation, originalHandler, typeEvent]; - } - - function addHandler(element, originalTypeEvent, handler, delegationFn, oneOff) { - if (typeof originalTypeEvent !== 'string' || !element) { - return; - } + function addHandler (element, originalTypeEvent, handler, delegationFn, oneOff) { + if ( typeof originalTypeEvent !== 'string' || !element ) { + return; + } - if (!handler) { - handler = delegationFn; - delegationFn = null; - } // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position - // this prevents the handler from being dispatched the same way as mouseover or mouseout does + if ( ! handler ) { + handler = delegationFn; + delegationFn = null; + } // in case of mouseenter or mouseleave wrap the handler within a function that checks for its DOM position + // this prevents the handler from being dispatched the same way as mouseover or mouseout does + + if ( customEventsRegex.test(originalTypeEvent) ) { + const wrapFn = fn => { + return function (event) { + if ( !event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget) ) { + return fn.call(this, event); + } + }; + }; + + if ( delegationFn ) { + delegationFn = wrapFn(delegationFn); + } else { + handler = wrapFn(handler); + } + } + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn); + const events = getEvent(element); + const handlers = events[typeEvent] || (events[typeEvent] = {}); + const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null); - if (customEventsRegex.test(originalTypeEvent)) { - const wrapFn = fn => { - return function (event) { - if (!event.relatedTarget || event.relatedTarget !== event.delegateTarget && !event.delegateTarget.contains(event.relatedTarget)) { - return fn.call(this, event); - } - }; - }; + if ( previousFn ) { + previousFn.oneOff = previousFn.oneOff && oneOff; + return; + } - if (delegationFn) { - delegationFn = wrapFn(delegationFn); - } else { - handler = wrapFn(handler); - } + const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')); + const fn = delegation ? bootstrapDelegationHandler(element, handler, delegationFn) : bootstrapHandler(element, handler); + fn.delegationSelector = delegation ? handler : null; + fn.originalHandler = originalHandler; + fn.oneOff = oneOff; + fn.uidEvent = uid; + handlers[uid] = fn; + element.addEventListener(typeEvent, fn, delegation); } - const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn); - const events = getEvent(element); - const handlers = events[typeEvent] || (events[typeEvent] = {}); - const previousFn = findHandler(handlers, originalHandler, delegation ? handler : null); + function removeHandler (element, events, typeEvent, handler, delegationSelector) { + const fn = findHandler(events[typeEvent], handler, delegationSelector); - if (previousFn) { - previousFn.oneOff = previousFn.oneOff && oneOff; - return; - } + if ( ! fn ) { + return; + } - const uid = getUidEvent(originalHandler, originalTypeEvent.replace(namespaceRegex, '')); - const fn = delegation ? bootstrapDelegationHandler(element, handler, delegationFn) : bootstrapHandler(element, handler); - fn.delegationSelector = delegation ? handler : null; - fn.originalHandler = originalHandler; - fn.oneOff = oneOff; - fn.uidEvent = uid; - handlers[uid] = fn; - element.addEventListener(typeEvent, fn, delegation); - } - - function removeHandler(element, events, typeEvent, handler, delegationSelector) { - const fn = findHandler(events[typeEvent], handler, delegationSelector); - - if (!fn) { - return; + element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)); + delete events[typeEvent][fn.uidEvent]; } - element.removeEventListener(typeEvent, fn, Boolean(delegationSelector)); - delete events[typeEvent][fn.uidEvent]; - } - - function removeNamespacedHandlers(element, events, typeEvent, namespace) { - const storeElementEvent = events[typeEvent] || {}; - Object.keys(storeElementEvent).forEach(handlerKey => { - if (handlerKey.includes(namespace)) { - const event = storeElementEvent[handlerKey]; - removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector); - } - }); - } + function removeNamespacedHandlers (element, events, typeEvent, namespace) { + const storeElementEvent = events[typeEvent] || {}; + Object.keys(storeElementEvent).forEach(handlerKey => { + if ( handlerKey.includes(namespace) ) { + const event = storeElementEvent[handlerKey]; + removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector); + } + }); + } - function getTypeEvent(event) { + function getTypeEvent (event) { // allow to get the native events from namespaced events ('click.bs.button' --> 'click') - event = event.replace(stripNameRegex, ''); - return customEvents[event] || event; - } - - const EventHandler = { - on(element, event, handler, delegationFn) { - addHandler(element, event, handler, delegationFn, false); - }, - - one(element, event, handler, delegationFn) { - addHandler(element, event, handler, delegationFn, true); - }, - - off(element, originalTypeEvent, handler, delegationFn) { - if (typeof originalTypeEvent !== 'string' || !element) { - return; - } - - const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn); - const inNamespace = typeEvent !== originalTypeEvent; - const events = getEvent(element); - const isNamespace = originalTypeEvent.startsWith('.'); - - if (typeof originalHandler !== 'undefined') { - // Simplest case: handler is passed, remove that listener ONLY. - if (!events || !events[typeEvent]) { - return; - } - - removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null); - return; - } - - if (isNamespace) { - Object.keys(events).forEach(elementEvent => { - removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); - }); - } + event = event.replace(stripNameRegex, ''); + return customEvents[event] || event; + } + + const EventHandler = { + on (element, event, handler, delegationFn) { + addHandler(element, event, handler, delegationFn, false); + }, - const storeElementEvent = events[typeEvent] || {}; - Object.keys(storeElementEvent).forEach(keyHandlers => { - const handlerKey = keyHandlers.replace(stripUidRegex, ''); + one (element, event, handler, delegationFn) { + addHandler(element, event, handler, delegationFn, true); + }, - if (!inNamespace || originalTypeEvent.includes(handlerKey)) { - const event = storeElementEvent[keyHandlers]; - removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector); - } - }); - }, + off (element, originalTypeEvent, handler, delegationFn) { + if ( typeof originalTypeEvent !== 'string' || !element ) { + return; + } - trigger(element, event, args) { - if (typeof event !== 'string' || !element) { - return null; - } - - const $ = getjQuery(); - const typeEvent = getTypeEvent(event); - const inNamespace = event !== typeEvent; - const isNative = nativeEvents.has(typeEvent); - let jQueryEvent; - let bubbles = true; - let nativeDispatch = true; - let defaultPrevented = false; - let evt = null; - - if (inNamespace && $) { - jQueryEvent = $.Event(event, args); - $(element).trigger(jQueryEvent); - bubbles = !jQueryEvent.isPropagationStopped(); - nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); - defaultPrevented = jQueryEvent.isDefaultPrevented(); - } - - if (isNative) { - evt = document.createEvent('HTMLEvents'); - evt.initEvent(typeEvent, bubbles, true); - } else { - evt = new CustomEvent(event, { - bubbles, - cancelable: true - }); - } // merge custom information in our event + const [delegation, originalHandler, typeEvent] = normalizeParams(originalTypeEvent, handler, delegationFn); + const inNamespace = typeEvent !== originalTypeEvent; + const events = getEvent(element); + const isNamespace = originalTypeEvent.startsWith('.'); + if ( typeof originalHandler !== 'undefined' ) { + // Simplest case: handler is passed, remove that listener ONLY. + if ( !events || !events[typeEvent] ) { + return; + } - if (typeof args !== 'undefined') { - Object.keys(args).forEach(key => { - Object.defineProperty(evt, key, { - get() { - return args[key]; + removeHandler(element, events, typeEvent, originalHandler, delegation ? handler : null); + return; } - }); - }); - } + if ( isNamespace ) { + Object.keys(events).forEach(elementEvent => { + removeNamespacedHandlers(element, events, elementEvent, originalTypeEvent.slice(1)); + }); + } - if (defaultPrevented) { - evt.preventDefault(); - } + const storeElementEvent = events[typeEvent] || {}; + Object.keys(storeElementEvent).forEach(keyHandlers => { + const handlerKey = keyHandlers.replace(stripUidRegex, ''); - if (nativeDispatch) { - element.dispatchEvent(evt); - } + if ( !inNamespace || originalTypeEvent.includes(handlerKey) ) { + const event = storeElementEvent[keyHandlers]; + removeHandler(element, events, typeEvent, event.originalHandler, event.delegationSelector); + } + }); + }, - if (evt.defaultPrevented && typeof jQueryEvent !== 'undefined') { - jQueryEvent.preventDefault(); - } + trigger (element, event, args) { + if ( typeof event !== 'string' || !element ) { + return null; + } - return evt; - } + const $ = getjQuery(); + const typeEvent = getTypeEvent(event); + const inNamespace = event !== typeEvent; + const isNative = nativeEvents.has(typeEvent); + let jQueryEvent; + let bubbles = true; + let nativeDispatch = true; + let defaultPrevented = false; + let evt = null; + + if ( inNamespace && $ ) { + jQueryEvent = $.Event(event, args); + $(element).trigger(jQueryEvent); + bubbles = !jQueryEvent.isPropagationStopped(); + nativeDispatch = !jQueryEvent.isImmediatePropagationStopped(); + defaultPrevented = jQueryEvent.isDefaultPrevented(); + } + + if ( isNative ) { + evt = document.createEvent('HTMLEvents'); + evt.initEvent(typeEvent, bubbles, true); + } else { + evt = new CustomEvent(event, { + bubbles, + cancelable: true, + }); + } // merge custom information in our event + + if ( typeof args !== 'undefined' ) { + Object.keys(args).forEach(key => { + Object.defineProperty(evt, key, { + get () { + return args[key]; + }, + + }); + }); + } + + if ( defaultPrevented ) { + evt.preventDefault(); + } + + if ( nativeDispatch ) { + element.dispatchEvent(evt); + } + + if ( evt.defaultPrevented && typeof jQueryEvent !== 'undefined' ) { + jQueryEvent.preventDefault(); + } + + return evt; + }, - }; + }; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): dom/data.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const elementMap = new Map(); - const Data = { - set(element, key, instance) { - if (!elementMap.has(element)) { - elementMap.set(element, new Map()); - } - - const instanceMap = elementMap.get(element); // make it clear we only want one instance per element - // can be removed later when multiple key/instances are fine to be used - - if (!instanceMap.has(key) && instanceMap.size !== 0) { - // eslint-disable-next-line no-console - console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); - return; - } - - instanceMap.set(key, instance); - }, - - get(element, key) { - if (elementMap.has(element)) { - return elementMap.get(element).get(key) || null; - } - - return null; - }, - - remove(element, key) { - if (!elementMap.has(element)) { - return; - } - - const instanceMap = elementMap.get(element); - instanceMap.delete(key); // free up element references if there are no instances left for an element - - if (instanceMap.size === 0) { - elementMap.delete(element); - } - } + const elementMap = new Map(); + const Data = { + set (element, key, instance) { + if ( ! elementMap.has(element) ) { + elementMap.set(element, new Map()); + } + + const instanceMap = elementMap.get(element); // make it clear we only want one instance per element + // can be removed later when multiple key/instances are fine to be used + + if ( !instanceMap.has(key) && instanceMap.size !== 0 ) { + + console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(instanceMap.keys())[0]}.`); + return; + } + + instanceMap.set(key, instance); + }, - }; + get (element, key) { + if ( elementMap.has(element) ) { + return elementMap.get(element).get(key) || null; + } + + return null; + }, + + remove (element, key) { + if ( ! elementMap.has(element) ) { + return; + } + + const instanceMap = elementMap.get(element); + instanceMap.delete(key); // free up element references if there are no instances left for an element + + if ( instanceMap.size === 0 ) { + elementMap.delete(element); + } + }, + + }; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): base-component.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const VERSION = '5.1.3'; + const VERSION = '5.1.3'; - class BaseComponent { - constructor(element) { - element = getElement(element); + class BaseComponent { + constructor (element) { + element = getElement(element); - if (!element) { - return; - } + if ( ! element ) { + return; + } - this._element = element; - Data.set(this._element, this.constructor.DATA_KEY, this); - } + this._element = element; + Data.set(this._element, this.constructor.DATA_KEY, this); + } - dispose() { - Data.remove(this._element, this.constructor.DATA_KEY); - EventHandler.off(this._element, this.constructor.EVENT_KEY); - Object.getOwnPropertyNames(this).forEach(propertyName => { - this[propertyName] = null; - }); - } + dispose () { + Data.remove(this._element, this.constructor.DATA_KEY); + EventHandler.off(this._element, this.constructor.EVENT_KEY); + Object.getOwnPropertyNames(this).forEach(propertyName => { + this[propertyName] = null; + }); + } - _queueCallback(callback, element, isAnimated = true) { - executeAfterTransition(callback, element, isAnimated); - } - /** Static */ + _queueCallback (callback, element, isAnimated = true) { + executeAfterTransition(callback, element, isAnimated); + } + /** Static */ + static getInstance (element) { + return Data.get(getElement(element), this.DATA_KEY); + } - static getInstance(element) { - return Data.get(getElement(element), this.DATA_KEY); - } + static getOrCreateInstance (element, config = {}) { + return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); + } - static getOrCreateInstance(element, config = {}) { - return this.getInstance(element) || new this(element, typeof config === 'object' ? config : null); - } + static get VERSION () { + return VERSION; + } - static get VERSION() { - return VERSION; - } + static get NAME () { + throw new Error('You have to implement the static method "NAME", for each component!'); + } - static get NAME() { - throw new Error('You have to implement the static method "NAME", for each component!'); - } + static get DATA_KEY () { + return `bs.${this.NAME}`; + } - static get DATA_KEY() { - return `bs.${this.NAME}`; - } + static get EVENT_KEY () { + return `.${this.DATA_KEY}`; + } - static get EVENT_KEY() { - return `.${this.DATA_KEY}`; } - } - - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/component-functions.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const enableDismissTrigger = (component, method = 'hide') => { - const clickEvent = `click.dismiss${component.EVENT_KEY}`; - const name = component.NAME; - EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault(); - } + const enableDismissTrigger = (component, method = 'hide') => { + const clickEvent = `click.dismiss${component.EVENT_KEY}`; + const name = component.NAME; + EventHandler.on(document, clickEvent, `[data-bs-dismiss="${name}"]`, function (event) { + if ( ['A', 'AREA'].includes(this.tagName) ) { + event.preventDefault(); + } - if (isDisabled(this)) { - return; - } + if ( isDisabled(this) ) { + return; + } - const target = getElementFromSelector(this) || this.closest(`.${name}`); - const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method + const target = getElementFromSelector(this) || this.closest(`.${name}`); + const instance = component.getOrCreateInstance(target); // Method argument is left, for Alert and only, as it doesn't implement the 'hide' method - instance[method](); - }); - }; + instance[method](); + }); + }; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): alert.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$d = 'alert'; - const DATA_KEY$c = 'bs.alert'; - const EVENT_KEY$c = `.${DATA_KEY$c}`; - const EVENT_CLOSE = `close${EVENT_KEY$c}`; - const EVENT_CLOSED = `closed${EVENT_KEY$c}`; - const CLASS_NAME_FADE$5 = 'fade'; - const CLASS_NAME_SHOW$8 = 'show'; - /** + const NAME$d = 'alert'; + const DATA_KEY$c = 'bs.alert'; + const EVENT_KEY$c = `.${DATA_KEY$c}`; + const EVENT_CLOSE = `close${EVENT_KEY$c}`; + const EVENT_CLOSED = `closed${EVENT_KEY$c}`; + const CLASS_NAME_FADE$5 = 'fade'; + const CLASS_NAME_SHOW$8 = 'show'; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Alert extends BaseComponent { + class Alert extends BaseComponent { // Getters - static get NAME() { - return NAME$d; - } // Public - + static get NAME () { + return NAME$d; + } // Public - close() { - const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); + close () { + const closeEvent = EventHandler.trigger(this._element, EVENT_CLOSE); - if (closeEvent.defaultPrevented) { - return; - } - - this._element.classList.remove(CLASS_NAME_SHOW$8); + if ( closeEvent.defaultPrevented ) { + return; + } - const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); + this._element.classList.remove(CLASS_NAME_SHOW$8); - this._queueCallback(() => this._destroyElement(), this._element, isAnimated); - } // Private + const isAnimated = this._element.classList.contains(CLASS_NAME_FADE$5); + this._queueCallback(() => this._destroyElement(), this._element, isAnimated); + } // Private - _destroyElement() { - this._element.remove(); + _destroyElement () { + this._element.remove(); - EventHandler.trigger(this._element, EVENT_CLOSED); - this.dispose(); - } // Static + EventHandler.trigger(this._element, EVENT_CLOSED); + this.dispose(); + } // Static + static jQueryInterface (config) { + return this.each(function () { + const data = Alert.getOrCreateInstance(this); - static jQueryInterface(config) { - return this.each(function () { - const data = Alert.getOrCreateInstance(this); + if ( typeof config !== 'string' ) { + return; + } - if (typeof config !== 'string') { - return; - } + if ( data[config] === undefined || config.startsWith('_') || config === 'constructor' ) { + throw new TypeError(`No method named "${config}"`); + } - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`); + data[config](this); + }); } - data[config](this); - }); } - - } - /** + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ - - enableDismissTrigger(Alert, 'close'); - /** + enableDismissTrigger(Alert, 'close'); + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Alert to jQuery only if jQuery is present */ - defineJQueryPlugin(Alert); + defineJQueryPlugin(Alert); - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): button.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$c = 'button'; - const DATA_KEY$b = 'bs.button'; - const EVENT_KEY$b = `.${DATA_KEY$b}`; - const DATA_API_KEY$7 = '.data-api'; - const CLASS_NAME_ACTIVE$3 = 'active'; - const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; - const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$b}${DATA_API_KEY$7}`; - /** + const NAME$c = 'button'; + const DATA_KEY$b = 'bs.button'; + const EVENT_KEY$b = `.${DATA_KEY$b}`; + const DATA_API_KEY$7 = '.data-api'; + const CLASS_NAME_ACTIVE$3 = 'active'; + const SELECTOR_DATA_TOGGLE$5 = '[data-bs-toggle="button"]'; + const EVENT_CLICK_DATA_API$6 = `click${EVENT_KEY$b}${DATA_API_KEY$7}`; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Button extends BaseComponent { + class Button extends BaseComponent { // Getters - static get NAME() { - return NAME$c; - } // Public - - - toggle() { - // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method - this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); - } // Static - - - static jQueryInterface(config) { - return this.each(function () { - const data = Button.getOrCreateInstance(this); - - if (config === 'toggle') { - data[config](); + static get NAME () { + return NAME$c; + } // Public + + toggle () { + // Toggle class and sync the `aria-pressed` attribute with the return value of the `.toggle()` method + this._element.setAttribute('aria-pressed', this._element.classList.toggle(CLASS_NAME_ACTIVE$3)); + } // Static + + static jQueryInterface (config) { + return this.each(function () { + const data = Button.getOrCreateInstance(this); + + if ( config === 'toggle' ) { + data[config](); + } + }); } - }); - } - } - /** + } + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ - - EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { - event.preventDefault(); - const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); - const data = Button.getOrCreateInstance(button); - data.toggle(); - }); - /** + EventHandler.on(document, EVENT_CLICK_DATA_API$6, SELECTOR_DATA_TOGGLE$5, event => { + event.preventDefault(); + const button = event.target.closest(SELECTOR_DATA_TOGGLE$5); + const data = Button.getOrCreateInstance(button); + data.toggle(); + }); + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Button to jQuery only if jQuery is present */ - defineJQueryPlugin(Button); + defineJQueryPlugin(Button); - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): dom/manipulator.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - function normalizeData(val) { - if (val === 'true') { - return true; - } + function normalizeData (val) { + if ( val === 'true' ) { + return true; + } - if (val === 'false') { - return false; - } + if ( val === 'false' ) { + return false; + } - if (val === Number(val).toString()) { - return Number(val); - } + if ( val === Number(val).toString() ) { + return Number(val); + } + + if ( val === '' || val === 'null' ) { + return null; + } - if (val === '' || val === 'null') { - return null; + return val; } - return val; - } - - function normalizeDataKey(key) { - return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); - } - - const Manipulator = { - setDataAttribute(element, key, value) { - element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); - }, - - removeDataAttribute(element, key) { - element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); - }, - - getDataAttributes(element) { - if (!element) { - return {}; - } - - const attributes = {}; - Object.keys(element.dataset).filter(key => key.startsWith('bs')).forEach(key => { - let pureKey = key.replace(/^bs/, ''); - pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); - attributes[pureKey] = normalizeData(element.dataset[key]); - }); - return attributes; - }, - - getDataAttribute(element, key) { - return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); - }, - - offset(element) { - const rect = element.getBoundingClientRect(); - return { - top: rect.top + window.pageYOffset, - left: rect.left + window.pageXOffset - }; - }, - - position(element) { - return { - top: element.offsetTop, - left: element.offsetLeft - }; + function normalizeDataKey (key) { + return key.replace(/[A-Z]/g, chr => `-${chr.toLowerCase()}`); } - }; + const Manipulator = { + setDataAttribute (element, key, value) { + element.setAttribute(`data-bs-${normalizeDataKey(key)}`, value); + }, + + removeDataAttribute (element, key) { + element.removeAttribute(`data-bs-${normalizeDataKey(key)}`); + }, + + getDataAttributes (element) { + if ( ! element ) { + return {}; + } + + const attributes = {}; + Object.keys(element.dataset).filter(key => key.startsWith('bs')).forEach(key => { + let pureKey = key.replace(/^bs/, ''); + pureKey = pureKey.charAt(0).toLowerCase() + pureKey.slice(1, pureKey.length); + attributes[pureKey] = normalizeData(element.dataset[key]); + }); + return attributes; + }, + + getDataAttribute (element, key) { + return normalizeData(element.getAttribute(`data-bs-${normalizeDataKey(key)}`)); + }, + + offset (element) { + const rect = element.getBoundingClientRect(); + return { + top: rect.top + window.pageYOffset, + left: rect.left + window.pageXOffset, + }; + }, + + position (element) { + return { + top: element.offsetTop, + left: element.offsetLeft, + }; + }, + + }; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): dom/selector-engine.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const NODE_TEXT = 3; - const SelectorEngine = { - find(selector, element = document.documentElement) { - return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); - }, + const NODE_TEXT = 3; + const SelectorEngine = { + find (selector, element = document.documentElement) { + return [].concat(...Element.prototype.querySelectorAll.call(element, selector)); + }, - findOne(selector, element = document.documentElement) { - return Element.prototype.querySelector.call(element, selector); - }, + findOne (selector, element = document.documentElement) { + return Element.prototype.querySelector.call(element, selector); + }, - children(element, selector) { - return [].concat(...element.children).filter(child => child.matches(selector)); - }, + children (element, selector) { + return [].concat(...element.children).filter(child => child.matches(selector)); + }, - parents(element, selector) { - const parents = []; - let ancestor = element.parentNode; + parents (element, selector) { + const parents = []; + let ancestor = element.parentNode; - while (ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODE_TEXT) { - if (ancestor.matches(selector)) { - parents.push(ancestor); - } + while ( ancestor && ancestor.nodeType === Node.ELEMENT_NODE && ancestor.nodeType !== NODE_TEXT ) { + if ( ancestor.matches(selector) ) { + parents.push(ancestor); + } - ancestor = ancestor.parentNode; - } + ancestor = ancestor.parentNode; + } - return parents; - }, + return parents; + }, - prev(element, selector) { - let previous = element.previousElementSibling; + prev (element, selector) { + let previous = element.previousElementSibling; - while (previous) { - if (previous.matches(selector)) { - return [previous]; - } + while ( previous ) { + if ( previous.matches(selector) ) { + return [previous]; + } - previous = previous.previousElementSibling; - } + previous = previous.previousElementSibling; + } - return []; - }, + return []; + }, - next(element, selector) { - let next = element.nextElementSibling; + next (element, selector) { + let next = element.nextElementSibling; - while (next) { - if (next.matches(selector)) { - return [next]; - } + while ( next ) { + if ( next.matches(selector) ) { + return [next]; + } - next = next.nextElementSibling; - } + next = next.nextElementSibling; + } - return []; - }, + return []; + }, - focusableChildren(element) { - const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(', '); - return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); - } + focusableChildren (element) { + const focusables = ['a', 'button', 'input', 'textarea', 'select', 'details', '[tabindex]', '[contenteditable="true"]'].map(selector => `${selector}:not([tabindex^="-"])`).join(', '); + return this.find(focusables, element).filter(el => !isDisabled(el) && isVisible(el)); + }, - }; + }; - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): carousel.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$b = 'carousel'; - const DATA_KEY$a = 'bs.carousel'; - const EVENT_KEY$a = `.${DATA_KEY$a}`; - const DATA_API_KEY$6 = '.data-api'; - const ARROW_LEFT_KEY = 'ArrowLeft'; - const ARROW_RIGHT_KEY = 'ArrowRight'; - const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch - - const SWIPE_THRESHOLD = 40; - const Default$a = { - interval: 5000, - keyboard: true, - slide: false, - pause: 'hover', - wrap: true, - touch: true - }; - const DefaultType$a = { - interval: '(number|boolean)', - keyboard: 'boolean', - slide: '(boolean|string)', - pause: '(string|boolean)', - wrap: 'boolean', - touch: 'boolean' - }; - const ORDER_NEXT = 'next'; - const ORDER_PREV = 'prev'; - const DIRECTION_LEFT = 'left'; - const DIRECTION_RIGHT = 'right'; - const KEY_TO_DIRECTION = { - [ARROW_LEFT_KEY]: DIRECTION_RIGHT, - [ARROW_RIGHT_KEY]: DIRECTION_LEFT - }; - const EVENT_SLIDE = `slide${EVENT_KEY$a}`; - const EVENT_SLID = `slid${EVENT_KEY$a}`; - const EVENT_KEYDOWN = `keydown${EVENT_KEY$a}`; - const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY$a}`; - const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY$a}`; - const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$a}`; - const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$a}`; - const EVENT_TOUCHEND = `touchend${EVENT_KEY$a}`; - const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$a}`; - const EVENT_POINTERUP = `pointerup${EVENT_KEY$a}`; - const EVENT_DRAG_START = `dragstart${EVENT_KEY$a}`; - const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$a}${DATA_API_KEY$6}`; - const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; - const CLASS_NAME_CAROUSEL = 'carousel'; - const CLASS_NAME_ACTIVE$2 = 'active'; - const CLASS_NAME_SLIDE = 'slide'; - const CLASS_NAME_END = 'carousel-item-end'; - const CLASS_NAME_START = 'carousel-item-start'; - const CLASS_NAME_NEXT = 'carousel-item-next'; - const CLASS_NAME_PREV = 'carousel-item-prev'; - const CLASS_NAME_POINTER_EVENT = 'pointer-event'; - const SELECTOR_ACTIVE$1 = '.active'; - const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'; - const SELECTOR_ITEM = '.carousel-item'; - const SELECTOR_ITEM_IMG = '.carousel-item img'; - const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'; - const SELECTOR_INDICATORS = '.carousel-indicators'; - const SELECTOR_INDICATOR = '[data-bs-target]'; - const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'; - const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'; - const POINTER_TYPE_TOUCH = 'touch'; - const POINTER_TYPE_PEN = 'pen'; - /** - * ------------------------------------------------------------------------ + const NAME$b = 'carousel'; + const DATA_KEY$a = 'bs.carousel'; + const EVENT_KEY$a = `.${DATA_KEY$a}`; + const DATA_API_KEY$6 = '.data-api'; + const ARROW_LEFT_KEY = 'ArrowLeft'; + const ARROW_RIGHT_KEY = 'ArrowRight'; + const TOUCHEVENT_COMPAT_WAIT = 500; // Time for mouse compat events to fire after touch + + const SWIPE_THRESHOLD = 40; + const Default$a = { + interval: 5000, + keyboard: true, + slide: false, + pause: 'hover', + wrap: true, + touch: true, + }; + const DefaultType$a = { + interval: '(number|boolean)', + keyboard: 'boolean', + slide: '(boolean|string)', + pause: '(string|boolean)', + wrap: 'boolean', + touch: 'boolean', + }; + const ORDER_NEXT = 'next'; + const ORDER_PREV = 'prev'; + const DIRECTION_LEFT = 'left'; + const DIRECTION_RIGHT = 'right'; + const KEY_TO_DIRECTION = { + [ARROW_LEFT_KEY]: DIRECTION_RIGHT, + [ARROW_RIGHT_KEY]: DIRECTION_LEFT, + }; + const EVENT_SLIDE = `slide${EVENT_KEY$a}`; + const EVENT_SLID = `slid${EVENT_KEY$a}`; + const EVENT_KEYDOWN = `keydown${EVENT_KEY$a}`; + const EVENT_MOUSEENTER = `mouseenter${EVENT_KEY$a}`; + const EVENT_MOUSELEAVE = `mouseleave${EVENT_KEY$a}`; + const EVENT_TOUCHSTART = `touchstart${EVENT_KEY$a}`; + const EVENT_TOUCHMOVE = `touchmove${EVENT_KEY$a}`; + const EVENT_TOUCHEND = `touchend${EVENT_KEY$a}`; + const EVENT_POINTERDOWN = `pointerdown${EVENT_KEY$a}`; + const EVENT_POINTERUP = `pointerup${EVENT_KEY$a}`; + const EVENT_DRAG_START = `dragstart${EVENT_KEY$a}`; + const EVENT_LOAD_DATA_API$2 = `load${EVENT_KEY$a}${DATA_API_KEY$6}`; + const EVENT_CLICK_DATA_API$5 = `click${EVENT_KEY$a}${DATA_API_KEY$6}`; + const CLASS_NAME_CAROUSEL = 'carousel'; + const CLASS_NAME_ACTIVE$2 = 'active'; + const CLASS_NAME_SLIDE = 'slide'; + const CLASS_NAME_END = 'carousel-item-end'; + const CLASS_NAME_START = 'carousel-item-start'; + const CLASS_NAME_NEXT = 'carousel-item-next'; + const CLASS_NAME_PREV = 'carousel-item-prev'; + const CLASS_NAME_POINTER_EVENT = 'pointer-event'; + const SELECTOR_ACTIVE$1 = '.active'; + const SELECTOR_ACTIVE_ITEM = '.active.carousel-item'; + const SELECTOR_ITEM = '.carousel-item'; + const SELECTOR_ITEM_IMG = '.carousel-item img'; + const SELECTOR_NEXT_PREV = '.carousel-item-next, .carousel-item-prev'; + const SELECTOR_INDICATORS = '.carousel-indicators'; + const SELECTOR_INDICATOR = '[data-bs-target]'; + const SELECTOR_DATA_SLIDE = '[data-bs-slide], [data-bs-slide-to]'; + const SELECTOR_DATA_RIDE = '[data-bs-ride="carousel"]'; + const POINTER_TYPE_TOUCH = 'touch'; + const POINTER_TYPE_PEN = 'pen'; + /** + * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Carousel extends BaseComponent { - constructor(element, config) { - super(element); - this._items = null; - this._interval = null; - this._activeElement = null; - this._isPaused = false; - this._isSliding = false; - this.touchTimeout = null; - this.touchStartX = 0; - this.touchDeltaX = 0; - this._config = this._getConfig(config); - this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); - this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; - this._pointerEvent = Boolean(window.PointerEvent); - - this._addEventListeners(); - } // Getters - - - static get Default() { - return Default$a; - } - - static get NAME() { - return NAME$b; - } // Public - - - next() { - this._slide(ORDER_NEXT); - } - - nextWhenVisible() { - // Don't call next when the page isn't visible - // or the carousel or its parent isn't visible - if (!document.hidden && isVisible(this._element)) { - this.next(); - } - } - - prev() { - this._slide(ORDER_PREV); - } - - pause(event) { - if (!event) { - this._isPaused = true; - } - - if (SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element)) { - triggerTransitionEnd(this._element); - this.cycle(true); - } - - clearInterval(this._interval); - this._interval = null; - } - - cycle(event) { - if (!event) { - this._isPaused = false; - } + class Carousel extends BaseComponent { + constructor (element, config) { + super(element); + this._items = null; + this._interval = null; + this._activeElement = null; + this._isPaused = false; + this._isSliding = false; + this.touchTimeout = null; + this.touchStartX = 0; + this.touchDeltaX = 0; + this._config = this._getConfig(config); + this._indicatorsElement = SelectorEngine.findOne(SELECTOR_INDICATORS, this._element); + this._touchSupported = 'ontouchstart' in document.documentElement || navigator.maxTouchPoints > 0; + this._pointerEvent = Boolean(window.PointerEvent); + + this._addEventListeners(); + } // Getters + + static get Default () { + return Default$a; + } - if (this._interval) { - clearInterval(this._interval); - this._interval = null; - } + static get NAME () { + return NAME$b; + } // Public - if (this._config && this._config.interval && !this._isPaused) { - this._updateInterval(); + next () { + this._slide(ORDER_NEXT); + } - this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval); - } - } + nextWhenVisible () { + // Don't call next when the page isn't visible + // or the carousel or its parent isn't visible + if ( !document.hidden && isVisible(this._element) ) { + this.next(); + } + } - to(index) { - this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); + prev () { + this._slide(ORDER_PREV); + } - const activeIndex = this._getItemIndex(this._activeElement); + pause (event) { + if ( ! event ) { + this._isPaused = true; + } - if (index > this._items.length - 1 || index < 0) { - return; - } + if ( SelectorEngine.findOne(SELECTOR_NEXT_PREV, this._element) ) { + triggerTransitionEnd(this._element); + this.cycle(true); + } - if (this._isSliding) { - EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); - return; - } + clearInterval(this._interval); + this._interval = null; + } - if (activeIndex === index) { - this.pause(); - this.cycle(); - return; - } + cycle (event) { + if ( ! event ) { + this._isPaused = false; + } - const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; + if ( this._interval ) { + clearInterval(this._interval); + this._interval = null; + } - this._slide(order, this._items[index]); - } // Private + if ( this._config && this._config.interval && !this._isPaused ) { + this._updateInterval(); + this._interval = setInterval((document.visibilityState ? this.nextWhenVisible : this.next).bind(this), this._config.interval); + } + } - _getConfig(config) { - config = { ...Default$a, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' ? config : {}) - }; - typeCheckConfig(NAME$b, config, DefaultType$a); - return config; - } + to (index) { + this._activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); - _handleSwipe() { - const absDeltax = Math.abs(this.touchDeltaX); + const activeIndex = this._getItemIndex(this._activeElement); - if (absDeltax <= SWIPE_THRESHOLD) { - return; - } + if ( index > this._items.length - 1 || index < 0 ) { + return; + } - const direction = absDeltax / this.touchDeltaX; - this.touchDeltaX = 0; + if ( this._isSliding ) { + EventHandler.one(this._element, EVENT_SLID, () => this.to(index)); + return; + } - if (!direction) { - return; - } + if ( activeIndex === index ) { + this.pause(); + this.cycle(); + return; + } - this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT); - } + const order = index > activeIndex ? ORDER_NEXT : ORDER_PREV; - _addEventListeners() { - if (this._config.keyboard) { - EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)); - } + this._slide(order, this._items[index]); + } // Private - if (this._config.pause === 'hover') { - EventHandler.on(this._element, EVENT_MOUSEENTER, event => this.pause(event)); - EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event)); - } + _getConfig (config) { + config = { ...Default$a, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}), + }; + typeCheckConfig(NAME$b, config, DefaultType$a); + return config; + } - if (this._config.touch && this._touchSupported) { - this._addTouchEventListeners(); - } - } + _handleSwipe () { + const absDeltax = Math.abs(this.touchDeltaX); - _addTouchEventListeners() { - const hasPointerPenTouch = event => { - return this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); - }; + if ( absDeltax <= SWIPE_THRESHOLD ) { + return; + } - const start = event => { - if (hasPointerPenTouch(event)) { - this.touchStartX = event.clientX; - } else if (!this._pointerEvent) { - this.touchStartX = event.touches[0].clientX; - } - }; + const direction = absDeltax / this.touchDeltaX; + this.touchDeltaX = 0; - const move = event => { - // ensure swiping with one touch and not pinching - this.touchDeltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this.touchStartX; - }; + if ( ! direction ) { + return; + } - const end = event => { - if (hasPointerPenTouch(event)) { - this.touchDeltaX = event.clientX - this.touchStartX; + this._slide(direction > 0 ? DIRECTION_RIGHT : DIRECTION_LEFT); } - this._handleSwipe(); - - if (this._config.pause === 'hover') { - // If it's a touch-enabled device, mouseenter/leave are fired as - // part of the mouse compatibility events on first tap - the carousel - // would stop cycling until user tapped out of it; - // here, we listen for touchend, explicitly pause the carousel - // (as if it's the second time we tap on it, mouseenter compat event - // is NOT fired) and after a timeout (to allow for mouse compatibility - // events to fire) we explicitly restart cycling - this.pause(); + _addEventListeners () { + if ( this._config.keyboard ) { + EventHandler.on(this._element, EVENT_KEYDOWN, event => this._keydown(event)); + } - if (this.touchTimeout) { - clearTimeout(this.touchTimeout); - } + if ( this._config.pause === 'hover' ) { + EventHandler.on(this._element, EVENT_MOUSEENTER, event => this.pause(event)); + EventHandler.on(this._element, EVENT_MOUSELEAVE, event => this.cycle(event)); + } - this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval); + if ( this._config.touch && this._touchSupported ) { + this._addTouchEventListeners(); + } } - }; - SelectorEngine.find(SELECTOR_ITEM_IMG, this._element).forEach(itemImg => { - EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault()); - }); - - if (this._pointerEvent) { - EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event)); - EventHandler.on(this._element, EVENT_POINTERUP, event => end(event)); + _addTouchEventListeners () { + const hasPointerPenTouch = event => { + return this._pointerEvent && (event.pointerType === POINTER_TYPE_PEN || event.pointerType === POINTER_TYPE_TOUCH); + }; + + const start = event => { + if ( hasPointerPenTouch(event) ) { + this.touchStartX = event.clientX; + } else if ( ! this._pointerEvent ) { + this.touchStartX = event.touches[0].clientX; + } + }; + + const move = event => { + // ensure swiping with one touch and not pinching + this.touchDeltaX = event.touches && event.touches.length > 1 ? 0 : event.touches[0].clientX - this.touchStartX; + }; + + const end = event => { + if ( hasPointerPenTouch(event) ) { + this.touchDeltaX = event.clientX - this.touchStartX; + } + + this._handleSwipe(); + + if ( this._config.pause === 'hover' ) { + // If it's a touch-enabled device, mouseenter/leave are fired as + // part of the mouse compatibility events on first tap - the carousel + // would stop cycling until user tapped out of it; + // here, we listen for touchend, explicitly pause the carousel + // (as if it's the second time we tap on it, mouseenter compat event + // is NOT fired) and after a timeout (to allow for mouse compatibility + // events to fire) we explicitly restart cycling + this.pause(); + + if ( this.touchTimeout ) { + clearTimeout(this.touchTimeout); + } + + this.touchTimeout = setTimeout(event => this.cycle(event), TOUCHEVENT_COMPAT_WAIT + this._config.interval); + } + }; + + SelectorEngine.find(SELECTOR_ITEM_IMG, this._element).forEach(itemImg => { + EventHandler.on(itemImg, EVENT_DRAG_START, event => event.preventDefault()); + }); - this._element.classList.add(CLASS_NAME_POINTER_EVENT); - } else { - EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event)); - EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event)); - EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event)); - } - } + if ( this._pointerEvent ) { + EventHandler.on(this._element, EVENT_POINTERDOWN, event => start(event)); + EventHandler.on(this._element, EVENT_POINTERUP, event => end(event)); - _keydown(event) { - if (/input|textarea/i.test(event.target.tagName)) { - return; - } + this._element.classList.add(CLASS_NAME_POINTER_EVENT); + } else { + EventHandler.on(this._element, EVENT_TOUCHSTART, event => start(event)); + EventHandler.on(this._element, EVENT_TOUCHMOVE, event => move(event)); + EventHandler.on(this._element, EVENT_TOUCHEND, event => end(event)); + } + } - const direction = KEY_TO_DIRECTION[event.key]; + _keydown (event) { + if ( /input|textarea/i.test(event.target.tagName) ) { + return; + } - if (direction) { - event.preventDefault(); + const direction = KEY_TO_DIRECTION[event.key]; - this._slide(direction); - } - } + if ( direction ) { + event.preventDefault(); - _getItemIndex(element) { - this._items = element && element.parentNode ? SelectorEngine.find(SELECTOR_ITEM, element.parentNode) : []; - return this._items.indexOf(element); - } + this._slide(direction); + } + } - _getItemByOrder(order, activeElement) { - const isNext = order === ORDER_NEXT; - return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap); - } + _getItemIndex (element) { + this._items = element && element.parentNode ? SelectorEngine.find(SELECTOR_ITEM, element.parentNode) : []; + return this._items.indexOf(element); + } - _triggerSlideEvent(relatedTarget, eventDirectionName) { - const targetIndex = this._getItemIndex(relatedTarget); + _getItemByOrder (order, activeElement) { + const isNext = order === ORDER_NEXT; + return getNextActiveElement(this._items, activeElement, isNext, this._config.wrap); + } - const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)); + _triggerSlideEvent (relatedTarget, eventDirectionName) { + const targetIndex = this._getItemIndex(relatedTarget); - return EventHandler.trigger(this._element, EVENT_SLIDE, { - relatedTarget, - direction: eventDirectionName, - from: fromIndex, - to: targetIndex - }); - } + const fromIndex = this._getItemIndex(SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element)); - _setActiveIndicatorElement(element) { - if (this._indicatorsElement) { - const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE$1, this._indicatorsElement); - activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); - activeIndicator.removeAttribute('aria-current'); - const indicators = SelectorEngine.find(SELECTOR_INDICATOR, this._indicatorsElement); - - for (let i = 0; i < indicators.length; i++) { - if (Number.parseInt(indicators[i].getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(element)) { - indicators[i].classList.add(CLASS_NAME_ACTIVE$2); - indicators[i].setAttribute('aria-current', 'true'); - break; - } + return EventHandler.trigger(this._element, EVENT_SLIDE, { + relatedTarget, + direction: eventDirectionName, + from: fromIndex, + to: targetIndex, + }); } - } - } - _updateInterval() { - const element = this._activeElement || SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); - - if (!element) { - return; - } + _setActiveIndicatorElement (element) { + if ( this._indicatorsElement ) { + const activeIndicator = SelectorEngine.findOne(SELECTOR_ACTIVE$1, this._indicatorsElement); + activeIndicator.classList.remove(CLASS_NAME_ACTIVE$2); + activeIndicator.removeAttribute('aria-current'); + const indicators = SelectorEngine.find(SELECTOR_INDICATOR, this._indicatorsElement); + + for ( let i = 0; i < indicators.length; i++ ) { + if ( Number.parseInt(indicators[i].getAttribute('data-bs-slide-to'), 10) === this._getItemIndex(element) ) { + indicators[i].classList.add(CLASS_NAME_ACTIVE$2); + indicators[i].setAttribute('aria-current', 'true'); + break; + } + } + } + } - const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); + _updateInterval () { + const element = this._activeElement || SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); - if (elementInterval) { - this._config.defaultInterval = this._config.defaultInterval || this._config.interval; - this._config.interval = elementInterval; - } else { - this._config.interval = this._config.defaultInterval || this._config.interval; - } - } + if ( ! element ) { + return; + } - _slide(directionOrOrder, element) { - const order = this._directionToOrder(directionOrOrder); + const elementInterval = Number.parseInt(element.getAttribute('data-bs-interval'), 10); - const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); + if ( elementInterval ) { + this._config.defaultInterval = this._config.defaultInterval || this._config.interval; + this._config.interval = elementInterval; + } else { + this._config.interval = this._config.defaultInterval || this._config.interval; + } + } - const activeElementIndex = this._getItemIndex(activeElement); + _slide (directionOrOrder, element) { + const order = this._directionToOrder(directionOrOrder); - const nextElement = element || this._getItemByOrder(order, activeElement); + const activeElement = SelectorEngine.findOne(SELECTOR_ACTIVE_ITEM, this._element); - const nextElementIndex = this._getItemIndex(nextElement); + const activeElementIndex = this._getItemIndex(activeElement); - const isCycling = Boolean(this._interval); - const isNext = order === ORDER_NEXT; - const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; - const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; + const nextElement = element || this._getItemByOrder(order, activeElement); - const eventDirectionName = this._orderToDirection(order); + const nextElementIndex = this._getItemIndex(nextElement); - if (nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE$2)) { - this._isSliding = false; - return; - } + const isCycling = Boolean(this._interval); + const isNext = order === ORDER_NEXT; + const directionalClassName = isNext ? CLASS_NAME_START : CLASS_NAME_END; + const orderClassName = isNext ? CLASS_NAME_NEXT : CLASS_NAME_PREV; - if (this._isSliding) { - return; - } + const eventDirectionName = this._orderToDirection(order); - const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName); + if ( nextElement && nextElement.classList.contains(CLASS_NAME_ACTIVE$2) ) { + this._isSliding = false; + return; + } - if (slideEvent.defaultPrevented) { - return; - } + if ( this._isSliding ) { + return; + } - if (!activeElement || !nextElement) { - // Some weirdness is happening, so we bail - return; - } + const slideEvent = this._triggerSlideEvent(nextElement, eventDirectionName); - this._isSliding = true; + if ( slideEvent.defaultPrevented ) { + return; + } - if (isCycling) { - this.pause(); - } + if ( !activeElement || !nextElement ) { + // Some weirdness is happening, so we bail + return; + } - this._setActiveIndicatorElement(nextElement); + this._isSliding = true; - this._activeElement = nextElement; + if ( isCycling ) { + this.pause(); + } - const triggerSlidEvent = () => { - EventHandler.trigger(this._element, EVENT_SLID, { - relatedTarget: nextElement, - direction: eventDirectionName, - from: activeElementIndex, - to: nextElementIndex - }); - }; - - if (this._element.classList.contains(CLASS_NAME_SLIDE)) { - nextElement.classList.add(orderClassName); - reflow(nextElement); - activeElement.classList.add(directionalClassName); - nextElement.classList.add(directionalClassName); - - const completeCallBack = () => { - nextElement.classList.remove(directionalClassName, orderClassName); - nextElement.classList.add(CLASS_NAME_ACTIVE$2); - activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName); - this._isSliding = false; - setTimeout(triggerSlidEvent, 0); - }; + this._setActiveIndicatorElement(nextElement); + + this._activeElement = nextElement; + + const triggerSlidEvent = () => { + EventHandler.trigger(this._element, EVENT_SLID, { + relatedTarget: nextElement, + direction: eventDirectionName, + from: activeElementIndex, + to: nextElementIndex, + }); + }; + + if ( this._element.classList.contains(CLASS_NAME_SLIDE) ) { + nextElement.classList.add(orderClassName); + reflow(nextElement); + activeElement.classList.add(directionalClassName); + nextElement.classList.add(directionalClassName); + + const completeCallBack = () => { + nextElement.classList.remove(directionalClassName, orderClassName); + nextElement.classList.add(CLASS_NAME_ACTIVE$2); + activeElement.classList.remove(CLASS_NAME_ACTIVE$2, orderClassName, directionalClassName); + this._isSliding = false; + setTimeout(triggerSlidEvent, 0); + }; + + this._queueCallback(completeCallBack, activeElement, true); + } else { + activeElement.classList.remove(CLASS_NAME_ACTIVE$2); + nextElement.classList.add(CLASS_NAME_ACTIVE$2); + this._isSliding = false; + triggerSlidEvent(); + } - this._queueCallback(completeCallBack, activeElement, true); - } else { - activeElement.classList.remove(CLASS_NAME_ACTIVE$2); - nextElement.classList.add(CLASS_NAME_ACTIVE$2); - this._isSliding = false; - triggerSlidEvent(); - } - - if (isCycling) { - this.cycle(); - } - } + if ( isCycling ) { + this.cycle(); + } + } - _directionToOrder(direction) { - if (![DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction)) { - return direction; - } + _directionToOrder (direction) { + if ( ! [DIRECTION_RIGHT, DIRECTION_LEFT].includes(direction) ) { + return direction; + } - if (isRTL()) { - return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; - } + if ( isRTL() ) { + return direction === DIRECTION_LEFT ? ORDER_PREV : ORDER_NEXT; + } - return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; - } + return direction === DIRECTION_LEFT ? ORDER_NEXT : ORDER_PREV; + } - _orderToDirection(order) { - if (![ORDER_NEXT, ORDER_PREV].includes(order)) { - return order; - } + _orderToDirection (order) { + if ( ! [ORDER_NEXT, ORDER_PREV].includes(order) ) { + return order; + } - if (isRTL()) { - return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; - } + if ( isRTL() ) { + return order === ORDER_PREV ? DIRECTION_LEFT : DIRECTION_RIGHT; + } - return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; - } // Static + return order === ORDER_PREV ? DIRECTION_RIGHT : DIRECTION_LEFT; + } // Static + static carouselInterface (element, config) { + const data = Carousel.getOrCreateInstance(element, config); + let { + _config, + } = data; - static carouselInterface(element, config) { - const data = Carousel.getOrCreateInstance(element, config); - let { - _config - } = data; + if ( typeof config === 'object' ) { + _config = { ..._config, + ...config, + }; + } - if (typeof config === 'object') { - _config = { ..._config, - ...config - }; - } + const action = typeof config === 'string' ? config : _config.slide; - const action = typeof config === 'string' ? config : _config.slide; + if ( typeof config === 'number' ) { + data.to(config); + } else if ( typeof action === 'string' ) { + if ( typeof data[action] === 'undefined' ) { + throw new TypeError(`No method named "${action}"`); + } - if (typeof config === 'number') { - data.to(config); - } else if (typeof action === 'string') { - if (typeof data[action] === 'undefined') { - throw new TypeError(`No method named "${action}"`); + data[action](); + } else if ( _config.interval && _config.ride ) { + data.pause(); + data.cycle(); + } } - data[action](); - } else if (_config.interval && _config.ride) { - data.pause(); - data.cycle(); - } - } + static jQueryInterface (config) { + return this.each(function () { + Carousel.carouselInterface(this, config); + }); + } - static jQueryInterface(config) { - return this.each(function () { - Carousel.carouselInterface(this, config); - }); - } + static dataApiClickHandler (event) { + const target = getElementFromSelector(this); - static dataApiClickHandler(event) { - const target = getElementFromSelector(this); + if ( !target || !target.classList.contains(CLASS_NAME_CAROUSEL) ) { + return; + } - if (!target || !target.classList.contains(CLASS_NAME_CAROUSEL)) { - return; - } + const config = { ...Manipulator.getDataAttributes(target), + ...Manipulator.getDataAttributes(this), + }; + const slideIndex = this.getAttribute('data-bs-slide-to'); - const config = { ...Manipulator.getDataAttributes(target), - ...Manipulator.getDataAttributes(this) - }; - const slideIndex = this.getAttribute('data-bs-slide-to'); + if ( slideIndex ) { + config.interval = false; + } - if (slideIndex) { - config.interval = false; - } + Carousel.carouselInterface(target, config); - Carousel.carouselInterface(target, config); + if ( slideIndex ) { + Carousel.getInstance(target).to(slideIndex); + } - if (slideIndex) { - Carousel.getInstance(target).to(slideIndex); - } + event.preventDefault(); + } - event.preventDefault(); } - - } - /** + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ + EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler); + EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => { + const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); - EventHandler.on(document, EVENT_CLICK_DATA_API$5, SELECTOR_DATA_SLIDE, Carousel.dataApiClickHandler); - EventHandler.on(window, EVENT_LOAD_DATA_API$2, () => { - const carousels = SelectorEngine.find(SELECTOR_DATA_RIDE); - - for (let i = 0, len = carousels.length; i < len; i++) { - Carousel.carouselInterface(carousels[i], Carousel.getInstance(carousels[i])); - } - }); - /** + for ( let i = 0, len = carousels.length; i < len; i++ ) { + Carousel.carouselInterface(carousels[i], Carousel.getInstance(carousels[i])); + } + }); + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Carousel to jQuery only if jQuery is present */ - defineJQueryPlugin(Carousel); + defineJQueryPlugin(Carousel); - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): collapse.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$a = 'collapse'; - const DATA_KEY$9 = 'bs.collapse'; - const EVENT_KEY$9 = `.${DATA_KEY$9}`; - const DATA_API_KEY$5 = '.data-api'; - const Default$9 = { - toggle: true, - parent: null - }; - const DefaultType$9 = { - toggle: 'boolean', - parent: '(null|element)' - }; - const EVENT_SHOW$5 = `show${EVENT_KEY$9}`; - const EVENT_SHOWN$5 = `shown${EVENT_KEY$9}`; - const EVENT_HIDE$5 = `hide${EVENT_KEY$9}`; - const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$9}`; - const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$9}${DATA_API_KEY$5}`; - const CLASS_NAME_SHOW$7 = 'show'; - const CLASS_NAME_COLLAPSE = 'collapse'; - const CLASS_NAME_COLLAPSING = 'collapsing'; - const CLASS_NAME_COLLAPSED = 'collapsed'; - const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`; - const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'; - const WIDTH = 'width'; - const HEIGHT = 'height'; - const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'; - const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle="collapse"]'; - /** + const NAME$a = 'collapse'; + const DATA_KEY$9 = 'bs.collapse'; + const EVENT_KEY$9 = `.${DATA_KEY$9}`; + const DATA_API_KEY$5 = '.data-api'; + const Default$9 = { + toggle: true, + parent: null, + }; + const DefaultType$9 = { + toggle: 'boolean', + parent: '(null|element)', + }; + const EVENT_SHOW$5 = `show${EVENT_KEY$9}`; + const EVENT_SHOWN$5 = `shown${EVENT_KEY$9}`; + const EVENT_HIDE$5 = `hide${EVENT_KEY$9}`; + const EVENT_HIDDEN$5 = `hidden${EVENT_KEY$9}`; + const EVENT_CLICK_DATA_API$4 = `click${EVENT_KEY$9}${DATA_API_KEY$5}`; + const CLASS_NAME_SHOW$7 = 'show'; + const CLASS_NAME_COLLAPSE = 'collapse'; + const CLASS_NAME_COLLAPSING = 'collapsing'; + const CLASS_NAME_COLLAPSED = 'collapsed'; + const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`; + const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'; + const WIDTH = 'width'; + const HEIGHT = 'height'; + const SELECTOR_ACTIVES = '.collapse.show, .collapse.collapsing'; + const SELECTOR_DATA_TOGGLE$4 = '[data-bs-toggle="collapse"]'; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Collapse extends BaseComponent { - constructor(element, config) { - super(element); - this._isTransitioning = false; - this._config = this._getConfig(config); - this._triggerArray = []; - const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); + class Collapse extends BaseComponent { + constructor (element, config) { + super(element); + this._isTransitioning = false; + this._config = this._getConfig(config); + this._triggerArray = []; + const toggleList = SelectorEngine.find(SELECTOR_DATA_TOGGLE$4); - for (let i = 0, len = toggleList.length; i < len; i++) { - const elem = toggleList[i]; - const selector = getSelectorFromElement(elem); - const filterElement = SelectorEngine.find(selector).filter(foundElem => foundElem === this._element); + for ( let i = 0, len = toggleList.length; i < len; i++ ) { + const elem = toggleList[i]; + const selector = getSelectorFromElement(elem); + const filterElement = SelectorEngine.find(selector).filter(foundElem => foundElem === this._element); - if (selector !== null && filterElement.length) { - this._selector = selector; - - this._triggerArray.push(elem); - } - } + if ( selector !== null && filterElement.length ) { + this._selector = selector; - this._initializeChildren(); - - if (!this._config.parent) { - this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); - } - - if (this._config.toggle) { - this.toggle(); - } - } // Getters + this._triggerArray.push(elem); + } + } + this._initializeChildren(); - static get Default() { - return Default$9; - } + if ( ! this._config.parent ) { + this._addAriaAndCollapsedClass(this._triggerArray, this._isShown()); + } - static get NAME() { - return NAME$a; - } // Public + if ( this._config.toggle ) { + this.toggle(); + } + } // Getters + static get Default () { + return Default$9; + } - toggle() { - if (this._isShown()) { - this.hide(); - } else { - this.show(); - } - } + static get NAME () { + return NAME$a; + } // Public - show() { - if (this._isTransitioning || this._isShown()) { - return; - } + toggle () { + if ( this._isShown() ) { + this.hide(); + } else { + this.show(); + } + } - let actives = []; - let activesData; + show () { + if ( this._isTransitioning || this._isShown() ) { + return; + } - if (this._config.parent) { - const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); - actives = SelectorEngine.find(SELECTOR_ACTIVES, this._config.parent).filter(elem => !children.includes(elem)); // remove children if greater depth - } + let actives = []; + let activesData; - const container = SelectorEngine.findOne(this._selector); + if ( this._config.parent ) { + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); + actives = SelectorEngine.find(SELECTOR_ACTIVES, this._config.parent).filter(elem => !children.includes(elem)); // remove children if greater depth + } - if (actives.length) { - const tempActiveData = actives.find(elem => container !== elem); - activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null; + const container = SelectorEngine.findOne(this._selector); - if (activesData && activesData._isTransitioning) { - return; - } - } + if ( actives.length ) { + const tempActiveData = actives.find(elem => container !== elem); + activesData = tempActiveData ? Collapse.getInstance(tempActiveData) : null; - const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$5); + if ( activesData && activesData._isTransitioning ) { + return; + } + } - if (startEvent.defaultPrevented) { - return; - } + const startEvent = EventHandler.trigger(this._element, EVENT_SHOW$5); - actives.forEach(elemActive => { - if (container !== elemActive) { - Collapse.getOrCreateInstance(elemActive, { - toggle: false - }).hide(); - } + if ( startEvent.defaultPrevented ) { + return; + } - if (!activesData) { - Data.set(elemActive, DATA_KEY$9, null); - } - }); + actives.forEach(elemActive => { + if ( container !== elemActive ) { + Collapse.getOrCreateInstance(elemActive, { + toggle: false, + }).hide(); + } - const dimension = this._getDimension(); + if ( ! activesData ) { + Data.set(elemActive, DATA_KEY$9, null); + } + }); - this._element.classList.remove(CLASS_NAME_COLLAPSE); + const dimension = this._getDimension(); - this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.classList.remove(CLASS_NAME_COLLAPSE); - this._element.style[dimension] = 0; + this._element.classList.add(CLASS_NAME_COLLAPSING); - this._addAriaAndCollapsedClass(this._triggerArray, true); + this._element.style[dimension] = 0; - this._isTransitioning = true; + this._addAriaAndCollapsedClass(this._triggerArray, true); - const complete = () => { - this._isTransitioning = false; + this._isTransitioning = true; - this._element.classList.remove(CLASS_NAME_COLLAPSING); + const complete = () => { + this._isTransitioning = false; - this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + this._element.classList.remove(CLASS_NAME_COLLAPSING); - this._element.style[dimension] = ''; - EventHandler.trigger(this._element, EVENT_SHOWN$5); - }; + this._element.classList.add(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); - const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); - const scrollSize = `scroll${capitalizedDimension}`; + this._element.style[dimension] = ''; + EventHandler.trigger(this._element, EVENT_SHOWN$5); + }; - this._queueCallback(complete, this._element, true); + const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1); + const scrollSize = `scroll${capitalizedDimension}`; - this._element.style[dimension] = `${this._element[scrollSize]}px`; - } + this._queueCallback(complete, this._element, true); - hide() { - if (this._isTransitioning || !this._isShown()) { - return; - } + this._element.style[dimension] = `${this._element[scrollSize]}px`; + } - const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$5); + hide () { + if ( this._isTransitioning || !this._isShown() ) { + return; + } - if (startEvent.defaultPrevented) { - return; - } + const startEvent = EventHandler.trigger(this._element, EVENT_HIDE$5); - const dimension = this._getDimension(); + if ( startEvent.defaultPrevented ) { + return; + } - this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; - reflow(this._element); + const dimension = this._getDimension(); - this._element.classList.add(CLASS_NAME_COLLAPSING); + this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`; + reflow(this._element); - this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); + this._element.classList.add(CLASS_NAME_COLLAPSING); - const triggerArrayLength = this._triggerArray.length; + this._element.classList.remove(CLASS_NAME_COLLAPSE, CLASS_NAME_SHOW$7); - for (let i = 0; i < triggerArrayLength; i++) { - const trigger = this._triggerArray[i]; - const elem = getElementFromSelector(trigger); + const triggerArrayLength = this._triggerArray.length; - if (elem && !this._isShown(elem)) { - this._addAriaAndCollapsedClass([trigger], false); - } - } + for ( let i = 0; i < triggerArrayLength; i++ ) { + const trigger = this._triggerArray[i]; + const elem = getElementFromSelector(trigger); - this._isTransitioning = true; + if ( elem && !this._isShown(elem) ) { + this._addAriaAndCollapsedClass([trigger], false); + } + } - const complete = () => { - this._isTransitioning = false; + this._isTransitioning = true; - this._element.classList.remove(CLASS_NAME_COLLAPSING); + const complete = () => { + this._isTransitioning = false; - this._element.classList.add(CLASS_NAME_COLLAPSE); + this._element.classList.remove(CLASS_NAME_COLLAPSING); - EventHandler.trigger(this._element, EVENT_HIDDEN$5); - }; + this._element.classList.add(CLASS_NAME_COLLAPSE); - this._element.style[dimension] = ''; + EventHandler.trigger(this._element, EVENT_HIDDEN$5); + }; - this._queueCallback(complete, this._element, true); - } + this._element.style[dimension] = ''; - _isShown(element = this._element) { - return element.classList.contains(CLASS_NAME_SHOW$7); - } // Private + this._queueCallback(complete, this._element, true); + } + _isShown (element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW$7); + } // Private - _getConfig(config) { - config = { ...Default$9, - ...Manipulator.getDataAttributes(this._element), - ...config - }; - config.toggle = Boolean(config.toggle); // Coerce string values + _getConfig (config) { + config = { ...Default$9, + ...Manipulator.getDataAttributes(this._element), + ...config, + }; + config.toggle = Boolean(config.toggle); // Coerce string values - config.parent = getElement(config.parent); - typeCheckConfig(NAME$a, config, DefaultType$9); - return config; - } + config.parent = getElement(config.parent); + typeCheckConfig(NAME$a, config, DefaultType$9); + return config; + } - _getDimension() { - return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; - } + _getDimension () { + return this._element.classList.contains(CLASS_NAME_HORIZONTAL) ? WIDTH : HEIGHT; + } - _initializeChildren() { - if (!this._config.parent) { - return; - } + _initializeChildren () { + if ( ! this._config.parent ) { + return; + } - const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); - SelectorEngine.find(SELECTOR_DATA_TOGGLE$4, this._config.parent).filter(elem => !children.includes(elem)).forEach(element => { - const selected = getElementFromSelector(element); + const children = SelectorEngine.find(CLASS_NAME_DEEPER_CHILDREN, this._config.parent); + SelectorEngine.find(SELECTOR_DATA_TOGGLE$4, this._config.parent).filter(elem => !children.includes(elem)).forEach(element => { + const selected = getElementFromSelector(element); - if (selected) { - this._addAriaAndCollapsedClass([element], this._isShown(selected)); + if ( selected ) { + this._addAriaAndCollapsedClass([element], this._isShown(selected)); + } + }); } - }); - } - _addAriaAndCollapsedClass(triggerArray, isOpen) { - if (!triggerArray.length) { - return; - } - - triggerArray.forEach(elem => { - if (isOpen) { - elem.classList.remove(CLASS_NAME_COLLAPSED); - } else { - elem.classList.add(CLASS_NAME_COLLAPSED); - } + _addAriaAndCollapsedClass (triggerArray, isOpen) { + if ( ! triggerArray.length ) { + return; + } - elem.setAttribute('aria-expanded', isOpen); - }); - } // Static + triggerArray.forEach(elem => { + if ( isOpen ) { + elem.classList.remove(CLASS_NAME_COLLAPSED); + } else { + elem.classList.add(CLASS_NAME_COLLAPSED); + } + elem.setAttribute('aria-expanded', isOpen); + }); + } // Static - static jQueryInterface(config) { - return this.each(function () { - const _config = {}; + static jQueryInterface (config) { + return this.each(function () { + const _config = {}; - if (typeof config === 'string' && /show|hide/.test(config)) { - _config.toggle = false; - } + if ( typeof config === 'string' && /show|hide/.test(config) ) { + _config.toggle = false; + } - const data = Collapse.getOrCreateInstance(this, _config); + const data = Collapse.getOrCreateInstance(this, _config); - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } + if ( typeof config === 'string' ) { + if ( typeof data[config] === 'undefined' ) { + throw new TypeError(`No method named "${config}"`); + } - data[config](); + data[config](); + } + }); } - }); - } - } - /** + } + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ - - EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { + EventHandler.on(document, EVENT_CLICK_DATA_API$4, SELECTOR_DATA_TOGGLE$4, function (event) { // preventDefault only for elements (which change the URL) not inside the collapsible element - if (event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A') { - event.preventDefault(); - } + if ( event.target.tagName === 'A' || event.delegateTarget && event.delegateTarget.tagName === 'A' ) { + event.preventDefault(); + } - const selector = getSelectorFromElement(this); - const selectorElements = SelectorEngine.find(selector); - selectorElements.forEach(element => { - Collapse.getOrCreateInstance(element, { - toggle: false - }).toggle(); + const selector = getSelectorFromElement(this); + const selectorElements = SelectorEngine.find(selector); + selectorElements.forEach(element => { + Collapse.getOrCreateInstance(element, { + toggle: false, + }).toggle(); + }); }); - }); - /** + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Collapse to jQuery only if jQuery is present */ - defineJQueryPlugin(Collapse); - - var top = 'top'; - var bottom = 'bottom'; - var right = 'right'; - var left = 'left'; - var auto = 'auto'; - var basePlacements = [top, bottom, right, left]; - var start = 'start'; - var end = 'end'; - var clippingParents = 'clippingParents'; - var viewport = 'viewport'; - var popper = 'popper'; - var reference = 'reference'; - var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) { - return acc.concat([placement + "-" + start, placement + "-" + end]); - }, []); - var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) { - return acc.concat([placement, placement + "-" + start, placement + "-" + end]); - }, []); // modifiers that need to read the DOM - - var beforeRead = 'beforeRead'; - var read = 'read'; - var afterRead = 'afterRead'; // pure-logic modifiers - - var beforeMain = 'beforeMain'; - var main = 'main'; - var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state) - - var beforeWrite = 'beforeWrite'; - var write = 'write'; - var afterWrite = 'afterWrite'; - var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite]; - - function getNodeName(element) { - return element ? (element.nodeName || '').toLowerCase() : null; - } - - function getWindow(node) { - if (node == null) { - return window; - } + defineJQueryPlugin(Collapse); + + var top = 'top'; + var bottom = 'bottom'; + var right = 'right'; + var left = 'left'; + var auto = 'auto'; + var basePlacements = [top, bottom, right, left]; + var start = 'start'; + var end = 'end'; + var clippingParents = 'clippingParents'; + var viewport = 'viewport'; + var popper = 'popper'; + var reference = 'reference'; + var variationPlacements = /*#__PURE__*/basePlacements.reduce(function (acc, placement) { + return acc.concat([`${placement }-${ start}`, `${placement }-${ end}`]); + }, []); + var placements = /*#__PURE__*/[].concat(basePlacements, [auto]).reduce(function (acc, placement) { + return acc.concat([placement, `${placement }-${ start}`, `${placement }-${ end}`]); + }, []); // modifiers that need to read the DOM + + var beforeRead = 'beforeRead'; + var read = 'read'; + var afterRead = 'afterRead'; // pure-logic modifiers + + var beforeMain = 'beforeMain'; + var main = 'main'; + var afterMain = 'afterMain'; // modifier with the purpose to write to the DOM (or write into a framework state) + + var beforeWrite = 'beforeWrite'; + var write = 'write'; + var afterWrite = 'afterWrite'; + var modifierPhases = [beforeRead, read, afterRead, beforeMain, main, afterMain, beforeWrite, write, afterWrite]; - if (node.toString() !== '[object Window]') { - var ownerDocument = node.ownerDocument; - return ownerDocument ? ownerDocument.defaultView || window : window; + function getNodeName (element) { + return element ? (element.nodeName || '').toLowerCase() : null; } - return node; - } + function getWindow (node) { + if ( node == null ) { + return window; + } - function isElement(node) { - var OwnElement = getWindow(node).Element; - return node instanceof OwnElement || node instanceof Element; - } + if ( node.toString() !== '[object Window]' ) { + var ownerDocument = node.ownerDocument; + return ownerDocument ? ownerDocument.defaultView || window : window; + } - function isHTMLElement(node) { - var OwnElement = getWindow(node).HTMLElement; - return node instanceof OwnElement || node instanceof HTMLElement; - } + return node; + } - function isShadowRoot(node) { + function isElement (node) { + var OwnElement = getWindow(node).Element; + return node instanceof OwnElement || node instanceof Element; + } + + function isHTMLElement (node) { + var OwnElement = getWindow(node).HTMLElement; + return node instanceof OwnElement || node instanceof HTMLElement; + } + + function isShadowRoot (node) { // IE 11 has no ShadowRoot - if (typeof ShadowRoot === 'undefined') { - return false; + if ( typeof ShadowRoot === 'undefined' ) { + return false; + } + + var OwnElement = getWindow(node).ShadowRoot; + return node instanceof OwnElement || node instanceof ShadowRoot; } - var OwnElement = getWindow(node).ShadowRoot; - return node instanceof OwnElement || node instanceof ShadowRoot; - } + // and applies them to the HTMLElements such as popper and arrow - // and applies them to the HTMLElements such as popper and arrow + function applyStyles (_ref) { + var state = _ref.state; + Object.keys(state.elements).forEach(function (name) { + var style = state.styles[name] || {}; + var attributes = state.attributes[name] || {}; + var element = state.elements[name]; // arrow is optional + virtual elements - function applyStyles(_ref) { - var state = _ref.state; - Object.keys(state.elements).forEach(function (name) { - var style = state.styles[name] || {}; - var attributes = state.attributes[name] || {}; - var element = state.elements[name]; // arrow is optional + virtual elements + if ( !isHTMLElement(element) || !getNodeName(element) ) { + return; + } // Flow doesn't support to extend this property, but it's the most + // effective way to apply styles to an HTMLElement + // $FlowFixMe[cannot-write] - if (!isHTMLElement(element) || !getNodeName(element)) { - return; - } // Flow doesn't support to extend this property, but it's the most - // effective way to apply styles to an HTMLElement - // $FlowFixMe[cannot-write] + Object.assign(element.style, style); + Object.keys(attributes).forEach(function (name) { + var value = attributes[name]; + if ( value === false ) { + element.removeAttribute(name); + } else { + element.setAttribute(name, value === true ? '' : value); + } + }); + }); + } - Object.assign(element.style, style); - Object.keys(attributes).forEach(function (name) { - var value = attributes[name]; + function effect$2 (_ref2) { + var state = _ref2.state; + var initialStyles = { + popper: { + position: state.options.strategy, + left: '0', + top: '0', + margin: '0', + }, + arrow: { + position: 'absolute', + }, + reference: {}, + }; + Object.assign(state.elements.popper.style, initialStyles.popper); + state.styles = initialStyles; - if (value === false) { - element.removeAttribute(name); - } else { - element.setAttribute(name, value === true ? '' : value); + if ( state.elements.arrow ) { + Object.assign(state.elements.arrow.style, initialStyles.arrow); } - }); - }); - } - - function effect$2(_ref2) { - var state = _ref2.state; - var initialStyles = { - popper: { - position: state.options.strategy, - left: '0', - top: '0', - margin: '0' - }, - arrow: { - position: 'absolute' - }, - reference: {} + + return function () { + Object.keys(state.elements).forEach(function (name) { + var element = state.elements[name]; + var attributes = state.attributes[name] || {}; + var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them + + var style = styleProperties.reduce(function (style, property) { + style[property] = ''; + return style; + }, {}); // arrow is optional + virtual elements + + if ( !isHTMLElement(element) || !getNodeName(element) ) { + return; + } + + Object.assign(element.style, style); + Object.keys(attributes).forEach(function (attribute) { + element.removeAttribute(attribute); + }); + }); + }; + } // eslint-disable-next-line import/no-unused-modules + + const applyStyles$1 = { + name: 'applyStyles', + enabled: true, + phase: 'write', + fn: applyStyles, + effect: effect$2, + requires: ['computeStyles'], }; - Object.assign(state.elements.popper.style, initialStyles.popper); - state.styles = initialStyles; - if (state.elements.arrow) { - Object.assign(state.elements.arrow.style, initialStyles.arrow); + function getBasePlacement (placement) { + return placement.split('-')[0]; + } + + // import { isHTMLElement } from './instanceOf'; + function getBoundingClientRect (element, // eslint-disable-next-line unused-imports/no-unused-vars + includeScale) { + + var rect = element.getBoundingClientRect(); + var scaleX = 1; + var scaleY = 1; // FIXME: + // `offsetWidth` returns an integer while `getBoundingClientRect` + // returns a float. This results in `scaleX` or `scaleY` being + // non-1 when it should be for elements that aren't a full pixel in + // width or height. + // if (isHTMLElement(element) && includeScale) { + // const offsetHeight = element.offsetHeight; + // const offsetWidth = element.offsetWidth; + // // Do not attempt to divide by 0, otherwise we get `Infinity` as scale + // // Fallback to 1 in case both values are `0` + // if (offsetWidth > 0) { + // scaleX = rect.width / offsetWidth || 1; + // } + // if (offsetHeight > 0) { + // scaleY = rect.height / offsetHeight || 1; + // } + // } + + return { + width: rect.width / scaleX, + height: rect.height / scaleY, + top: rect.top / scaleY, + right: rect.right / scaleX, + bottom: rect.bottom / scaleY, + left: rect.left / scaleX, + x: rect.left / scaleX, + y: rect.top / scaleY, + }; } - return function () { - Object.keys(state.elements).forEach(function (name) { - var element = state.elements[name]; - var attributes = state.attributes[name] || {}; - var styleProperties = Object.keys(state.styles.hasOwnProperty(name) ? state.styles[name] : initialStyles[name]); // Set all values to an empty string to unset them + // means it doesn't take into account transforms. - var style = styleProperties.reduce(function (style, property) { - style[property] = ''; - return style; - }, {}); // arrow is optional + virtual elements + function getLayoutRect (element) { + var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed. + // Fixes https://github.com/popperjs/popper-core/issues/1223 - if (!isHTMLElement(element) || !getNodeName(element)) { - return; + var width = element.offsetWidth; + var height = element.offsetHeight; + + if ( Math.abs(clientRect.width - width) <= 1 ) { + width = clientRect.width; } - Object.assign(element.style, style); - Object.keys(attributes).forEach(function (attribute) { - element.removeAttribute(attribute); - }); - }); - }; - } // eslint-disable-next-line import/no-unused-modules - - - const applyStyles$1 = { - name: 'applyStyles', - enabled: true, - phase: 'write', - fn: applyStyles, - effect: effect$2, - requires: ['computeStyles'] - }; - - function getBasePlacement(placement) { - return placement.split('-')[0]; - } - - // import { isHTMLElement } from './instanceOf'; - function getBoundingClientRect(element, // eslint-disable-next-line unused-imports/no-unused-vars - includeScale) { - - var rect = element.getBoundingClientRect(); - var scaleX = 1; - var scaleY = 1; // FIXME: - // `offsetWidth` returns an integer while `getBoundingClientRect` - // returns a float. This results in `scaleX` or `scaleY` being - // non-1 when it should be for elements that aren't a full pixel in - // width or height. - // if (isHTMLElement(element) && includeScale) { - // const offsetHeight = element.offsetHeight; - // const offsetWidth = element.offsetWidth; - // // Do not attempt to divide by 0, otherwise we get `Infinity` as scale - // // Fallback to 1 in case both values are `0` - // if (offsetWidth > 0) { - // scaleX = rect.width / offsetWidth || 1; - // } - // if (offsetHeight > 0) { - // scaleY = rect.height / offsetHeight || 1; - // } - // } - - return { - width: rect.width / scaleX, - height: rect.height / scaleY, - top: rect.top / scaleY, - right: rect.right / scaleX, - bottom: rect.bottom / scaleY, - left: rect.left / scaleX, - x: rect.left / scaleX, - y: rect.top / scaleY - }; - } + if ( Math.abs(clientRect.height - height) <= 1 ) { + height = clientRect.height; + } - // means it doesn't take into account transforms. + return { + x: element.offsetLeft, + y: element.offsetTop, + width: width, + height: height, + }; + } + + function contains (parent, child) { + var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method + + if ( parent.contains(child) ) { + return true; + } // then fallback to custom implementation with Shadow DOM support + else if ( rootNode && isShadowRoot(rootNode) ) { + var next = child; - function getLayoutRect(element) { - var clientRect = getBoundingClientRect(element); // Use the clientRect sizes if it's not been transformed. - // Fixes https://github.com/popperjs/popper-core/issues/1223 + do { + if ( next && parent.isSameNode(next) ) { + return true; + } // $FlowFixMe[prop-missing]: need a better way to handle this... - var width = element.offsetWidth; - var height = element.offsetHeight; + next = next.parentNode || next.host; + } while ( next ); + } // Give up, the result is false - if (Math.abs(clientRect.width - width) <= 1) { - width = clientRect.width; + return false; } - if (Math.abs(clientRect.height - height) <= 1) { - height = clientRect.height; + function getComputedStyle$1 (element) { + return getWindow(element).getComputedStyle(element); } - return { - x: element.offsetLeft, - y: element.offsetTop, - width: width, - height: height - }; - } + function isTableElement (element) { + return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0; + } - function contains(parent, child) { - var rootNode = child.getRootNode && child.getRootNode(); // First, attempt with faster native method + function getDocumentElement (element) { + // $FlowFixMe[incompatible-return]: assume body is always available + return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing] + element.document) || window.document).documentElement; + } - if (parent.contains(child)) { - return true; - } // then fallback to custom implementation with Shadow DOM support - else if (rootNode && isShadowRoot(rootNode)) { - var next = child; + function getParentNode (element) { + if ( getNodeName(element) === 'html' ) { + return element; + } - do { - if (next && parent.isSameNode(next)) { - return true; - } // $FlowFixMe[prop-missing]: need a better way to handle this... + return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle + // $FlowFixMe[incompatible-return] + // $FlowFixMe[prop-missing] + element.assignedSlot || // step into the shadow DOM of the parent of a slotted node + element.parentNode || ( // DOM Element detected + isShadowRoot(element) ? element.host : null) || // ShadowRoot detected + // $FlowFixMe[incompatible-call]: HTMLElement is a Node + getDocumentElement(element) // fallback + ); + } - next = next.parentNode || next.host; - } while (next); - } // Give up, the result is false + function getTrueOffsetParent (element) { + if ( !isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837 + getComputedStyle$1(element).position === 'fixed' ) { + return null; + } + return element.offsetParent; + } // `.offsetParent` reports `null` for fixed elements, while absolute elements + // return the containing block - return false; - } + function getContainingBlock (element) { + var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; + var isIE = navigator.userAgent.indexOf('Trident') !== -1; - function getComputedStyle$1(element) { - return getWindow(element).getComputedStyle(element); - } + if ( isIE && isHTMLElement(element) ) { + // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport + var elementCss = getComputedStyle$1(element); - function isTableElement(element) { - return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0; - } + if ( elementCss.position === 'fixed' ) { + return null; + } + } - function getDocumentElement(element) { - // $FlowFixMe[incompatible-return]: assume body is always available - return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing] - element.document) || window.document).documentElement; - } + var currentNode = getParentNode(element); - function getParentNode(element) { - if (getNodeName(element) === 'html') { - return element; - } + while ( isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0 ) { + var css = getComputedStyle$1(currentNode); // This is non-exhaustive but covers the most common CSS properties that + // create a containing block. + // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block - return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle - // $FlowFixMe[incompatible-return] - // $FlowFixMe[prop-missing] - element.assignedSlot || // step into the shadow DOM of the parent of a slotted node - element.parentNode || ( // DOM Element detected - isShadowRoot(element) ? element.host : null) || // ShadowRoot detected - // $FlowFixMe[incompatible-call]: HTMLElement is a Node - getDocumentElement(element) // fallback - - ); - } - - function getTrueOffsetParent(element) { - if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837 - getComputedStyle$1(element).position === 'fixed') { - return null; - } + if ( css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none' ) { + return currentNode; + } else { + currentNode = currentNode.parentNode; + } + } - return element.offsetParent; - } // `.offsetParent` reports `null` for fixed elements, while absolute elements - // return the containing block + return null; + } // Gets the closest ancestor positioned element. Handles some edge cases, + // such as table ancestors and cross browser bugs. + function getOffsetParent (element) { + var window = getWindow(element); + var offsetParent = getTrueOffsetParent(element); - function getContainingBlock(element) { - var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; - var isIE = navigator.userAgent.indexOf('Trident') !== -1; + while ( offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === 'static' ) { + offsetParent = getTrueOffsetParent(offsetParent); + } - if (isIE && isHTMLElement(element)) { - // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport - var elementCss = getComputedStyle$1(element); + if ( offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle$1(offsetParent).position === 'static') ) { + return window; + } - if (elementCss.position === 'fixed') { - return null; - } + return offsetParent || getContainingBlock(element) || window; } - var currentNode = getParentNode(element); - - while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) { - var css = getComputedStyle$1(currentNode); // This is non-exhaustive but covers the most common CSS properties that - // create a containing block. - // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block - - if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') { - return currentNode; - } else { - currentNode = currentNode.parentNode; - } + function getMainAxisFromPlacement (placement) { + return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y'; } - return null; - } // Gets the closest ancestor positioned element. Handles some edge cases, - // such as table ancestors and cross browser bugs. + var max = Math.max; + var min = Math.min; + var round = Math.round; + function within (min$1, value, max$1) { + return max(min$1, min(value, max$1)); + } - function getOffsetParent(element) { - var window = getWindow(element); - var offsetParent = getTrueOffsetParent(element); + function getFreshSideObject () { + return { + top: 0, + right: 0, + bottom: 0, + left: 0, + }; + } - while (offsetParent && isTableElement(offsetParent) && getComputedStyle$1(offsetParent).position === 'static') { - offsetParent = getTrueOffsetParent(offsetParent); + function mergePaddingObject (paddingObject) { + return Object.assign({}, getFreshSideObject(), paddingObject); } - if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle$1(offsetParent).position === 'static')) { - return window; + function expandToHashMap (value, keys) { + return keys.reduce(function (hashMap, key) { + hashMap[key] = value; + return hashMap; + }, {}); } - return offsetParent || getContainingBlock(element) || window; - } + var toPaddingObject = function toPaddingObject (padding, state) { + padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, { + placement: state.placement, + })) : padding; + return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); + }; - function getMainAxisFromPlacement(placement) { - return ['top', 'bottom'].indexOf(placement) >= 0 ? 'x' : 'y'; - } + function arrow (_ref) { + var _state$modifiersData$; - var max = Math.max; - var min = Math.min; - var round = Math.round; + var state = _ref.state, + name = _ref.name, + options = _ref.options; + var arrowElement = state.elements.arrow; + var popperOffsets = state.modifiersData.popperOffsets; + var basePlacement = getBasePlacement(state.placement); + var axis = getMainAxisFromPlacement(basePlacement); + var isVertical = [left, right].indexOf(basePlacement) >= 0; + var len = isVertical ? 'height' : 'width'; - function within(min$1, value, max$1) { - return max(min$1, min(value, max$1)); - } + if ( !arrowElement || !popperOffsets ) { + return; + } - function getFreshSideObject() { - return { - top: 0, - right: 0, - bottom: 0, - left: 0 - }; - } - - function mergePaddingObject(paddingObject) { - return Object.assign({}, getFreshSideObject(), paddingObject); - } - - function expandToHashMap(value, keys) { - return keys.reduce(function (hashMap, key) { - hashMap[key] = value; - return hashMap; - }, {}); - } - - var toPaddingObject = function toPaddingObject(padding, state) { - padding = typeof padding === 'function' ? padding(Object.assign({}, state.rects, { - placement: state.placement - })) : padding; - return mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); - }; - - function arrow(_ref) { - var _state$modifiersData$; - - var state = _ref.state, - name = _ref.name, - options = _ref.options; - var arrowElement = state.elements.arrow; - var popperOffsets = state.modifiersData.popperOffsets; - var basePlacement = getBasePlacement(state.placement); - var axis = getMainAxisFromPlacement(basePlacement); - var isVertical = [left, right].indexOf(basePlacement) >= 0; - var len = isVertical ? 'height' : 'width'; - - if (!arrowElement || !popperOffsets) { - return; - } + var paddingObject = toPaddingObject(options.padding, state); + var arrowRect = getLayoutRect(arrowElement); + var minProp = axis === 'y' ? top : left; + var maxProp = axis === 'y' ? bottom : right; + var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len]; + var startDiff = popperOffsets[axis] - state.rects.reference[axis]; + var arrowOffsetParent = getOffsetParent(arrowElement); + var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0; + var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is + // outside of the popper bounds + + var min = paddingObject[minProp]; + var max = clientSize - arrowRect[len] - paddingObject[maxProp]; + var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference; + var offset = within(min, center, max); // Prevents breaking syntax highlighting... + + var axisProp = axis; + state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$); + } + + function effect$1 (_ref2) { + var state = _ref2.state, + options = _ref2.options; + var _options$element = options.element, + arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element; + + if ( arrowElement == null ) { + return; + } // CSS selector - var paddingObject = toPaddingObject(options.padding, state); - var arrowRect = getLayoutRect(arrowElement); - var minProp = axis === 'y' ? top : left; - var maxProp = axis === 'y' ? bottom : right; - var endDiff = state.rects.reference[len] + state.rects.reference[axis] - popperOffsets[axis] - state.rects.popper[len]; - var startDiff = popperOffsets[axis] - state.rects.reference[axis]; - var arrowOffsetParent = getOffsetParent(arrowElement); - var clientSize = arrowOffsetParent ? axis === 'y' ? arrowOffsetParent.clientHeight || 0 : arrowOffsetParent.clientWidth || 0 : 0; - var centerToReference = endDiff / 2 - startDiff / 2; // Make sure the arrow doesn't overflow the popper if the center point is - // outside of the popper bounds - - var min = paddingObject[minProp]; - var max = clientSize - arrowRect[len] - paddingObject[maxProp]; - var center = clientSize / 2 - arrowRect[len] / 2 + centerToReference; - var offset = within(min, center, max); // Prevents breaking syntax highlighting... - - var axisProp = axis; - state.modifiersData[name] = (_state$modifiersData$ = {}, _state$modifiersData$[axisProp] = offset, _state$modifiersData$.centerOffset = offset - center, _state$modifiersData$); - } - - function effect$1(_ref2) { - var state = _ref2.state, - options = _ref2.options; - var _options$element = options.element, - arrowElement = _options$element === void 0 ? '[data-popper-arrow]' : _options$element; - - if (arrowElement == null) { - return; - } // CSS selector - - - if (typeof arrowElement === 'string') { - arrowElement = state.elements.popper.querySelector(arrowElement); - - if (!arrowElement) { - return; - } - } + if ( typeof arrowElement === 'string' ) { + arrowElement = state.elements.popper.querySelector(arrowElement); - if (!contains(state.elements.popper, arrowElement)) { + if ( ! arrowElement ) { + return; + } + } - return; - } + if ( ! contains(state.elements.popper, arrowElement) ) { - state.elements.arrow = arrowElement; - } // eslint-disable-next-line import/no-unused-modules - - - const arrow$1 = { - name: 'arrow', - enabled: true, - phase: 'main', - fn: arrow, - effect: effect$1, - requires: ['popperOffsets'], - requiresIfExists: ['preventOverflow'] - }; - - function getVariation(placement) { - return placement.split('-')[1]; - } - - var unsetSides = { - top: 'auto', - right: 'auto', - bottom: 'auto', - left: 'auto' - }; // Round the offsets to the nearest suitable subpixel based on the DPR. - // Zooming can change the DPR, but it seems to report a value that will - // cleanly divide the values into the appropriate subpixels. - - function roundOffsetsByDPR(_ref) { - var x = _ref.x, - y = _ref.y; - var win = window; - var dpr = win.devicePixelRatio || 1; - return { - x: round(round(x * dpr) / dpr) || 0, - y: round(round(y * dpr) / dpr) || 0 + return; + } + + state.elements.arrow = arrowElement; + } // eslint-disable-next-line import/no-unused-modules + + const arrow$1 = { + name: 'arrow', + enabled: true, + phase: 'main', + fn: arrow, + effect: effect$1, + requires: ['popperOffsets'], + requiresIfExists: ['preventOverflow'], }; - } - function mapToStyles(_ref2) { - var _Object$assign2; + function getVariation (placement) { + return placement.split('-')[1]; + } + + var unsetSides = { + top: 'auto', + right: 'auto', + bottom: 'auto', + left: 'auto', + }; // Round the offsets to the nearest suitable subpixel based on the DPR. + // Zooming can change the DPR, but it seems to report a value that will + // cleanly divide the values into the appropriate subpixels. + + function roundOffsetsByDPR (_ref) { + var x = _ref.x, + y = _ref.y; + var win = window; + var dpr = win.devicePixelRatio || 1; + return { + x: round(round(x * dpr) / dpr) || 0, + y: round(round(y * dpr) / dpr) || 0, + }; + } - var popper = _ref2.popper, - popperRect = _ref2.popperRect, - placement = _ref2.placement, - variation = _ref2.variation, - offsets = _ref2.offsets, - position = _ref2.position, - gpuAcceleration = _ref2.gpuAcceleration, - adaptive = _ref2.adaptive, - roundOffsets = _ref2.roundOffsets; + function mapToStyles (_ref2) { + var _Object$assign2; + + var popper = _ref2.popper, + popperRect = _ref2.popperRect, + placement = _ref2.placement, + variation = _ref2.variation, + offsets = _ref2.offsets, + position = _ref2.position, + gpuAcceleration = _ref2.gpuAcceleration, + adaptive = _ref2.adaptive, + roundOffsets = _ref2.roundOffsets; + + var _ref3 = roundOffsets === true ? roundOffsetsByDPR(offsets) : typeof roundOffsets === 'function' ? roundOffsets(offsets) : offsets, + _ref3$x = _ref3.x, + x = _ref3$x === void 0 ? 0 : _ref3$x, + _ref3$y = _ref3.y, + y = _ref3$y === void 0 ? 0 : _ref3$y; + + var hasX = offsets.hasOwnProperty('x'); + var hasY = offsets.hasOwnProperty('y'); + var sideX = left; + var sideY = top; + var win = window; + + if ( adaptive ) { + var offsetParent = getOffsetParent(popper); + var heightProp = 'clientHeight'; + var widthProp = 'clientWidth'; + + if ( offsetParent === getWindow(popper) ) { + offsetParent = getDocumentElement(popper); + + if ( getComputedStyle$1(offsetParent).position !== 'static' && position === 'absolute' ) { + heightProp = 'scrollHeight'; + widthProp = 'scrollWidth'; + } + } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it + + offsetParent = offsetParent; + + if ( placement === top || (placement === left || placement === right) && variation === end ) { + sideY = bottom; // $FlowFixMe[prop-missing] + + y -= offsetParent[heightProp] - popperRect.height; + y *= gpuAcceleration ? 1 : -1; + } - var _ref3 = roundOffsets === true ? roundOffsetsByDPR(offsets) : typeof roundOffsets === 'function' ? roundOffsets(offsets) : offsets, - _ref3$x = _ref3.x, - x = _ref3$x === void 0 ? 0 : _ref3$x, - _ref3$y = _ref3.y, - y = _ref3$y === void 0 ? 0 : _ref3$y; + if ( placement === left || (placement === top || placement === bottom) && variation === end ) { + sideX = right; // $FlowFixMe[prop-missing] - var hasX = offsets.hasOwnProperty('x'); - var hasY = offsets.hasOwnProperty('y'); - var sideX = left; - var sideY = top; - var win = window; + x -= offsetParent[widthProp] - popperRect.width; + x *= gpuAcceleration ? 1 : -1; + } + } - if (adaptive) { - var offsetParent = getOffsetParent(popper); - var heightProp = 'clientHeight'; - var widthProp = 'clientWidth'; + var commonStyles = Object.assign({ + position: position, + }, adaptive && unsetSides); - if (offsetParent === getWindow(popper)) { - offsetParent = getDocumentElement(popper); + if ( gpuAcceleration ) { + var _Object$assign; - if (getComputedStyle$1(offsetParent).position !== 'static' && position === 'absolute') { - heightProp = 'scrollHeight'; - widthProp = 'scrollWidth'; + return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? `translate(${ x }px, ${ y }px)` : `translate3d(${ x }px, ${ y }px, 0)`, _Object$assign)); } - } // $FlowFixMe[incompatible-cast]: force type refinement, we compare offsetParent with window above, but Flow doesn't detect it + return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? `${y }px` : '', _Object$assign2[sideX] = hasX ? `${x }px` : '', _Object$assign2.transform = '', _Object$assign2)); + } + + function computeStyles (_ref4) { + var state = _ref4.state, + options = _ref4.options; + var _options$gpuAccelerat = options.gpuAcceleration, + gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat, + _options$adaptive = options.adaptive, + adaptive = _options$adaptive === void 0 ? true : _options$adaptive, + _options$roundOffsets = options.roundOffsets, + roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; + + var commonStyles = { + placement: getBasePlacement(state.placement), + variation: getVariation(state.placement), + popper: state.elements.popper, + popperRect: state.rects.popper, + gpuAcceleration: gpuAcceleration, + }; - offsetParent = offsetParent; + if ( state.modifiersData.popperOffsets != null ) { + state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, { + offsets: state.modifiersData.popperOffsets, + position: state.options.strategy, + adaptive: adaptive, + roundOffsets: roundOffsets, + }))); + } - if (placement === top || (placement === left || placement === right) && variation === end) { - sideY = bottom; // $FlowFixMe[prop-missing] + if ( state.modifiersData.arrow != null ) { + state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, { + offsets: state.modifiersData.arrow, + position: 'absolute', + adaptive: false, + roundOffsets: roundOffsets, + }))); + } - y -= offsetParent[heightProp] - popperRect.height; - y *= gpuAcceleration ? 1 : -1; - } + state.attributes.popper = Object.assign({}, state.attributes.popper, { + 'data-popper-placement': state.placement, + }); + } // eslint-disable-next-line import/no-unused-modules + + const computeStyles$1 = { + name: 'computeStyles', + enabled: true, + phase: 'beforeWrite', + fn: computeStyles, + data: {}, + }; - if (placement === left || (placement === top || placement === bottom) && variation === end) { - sideX = right; // $FlowFixMe[prop-missing] + var passive = { + passive: true, + }; - x -= offsetParent[widthProp] - popperRect.width; - x *= gpuAcceleration ? 1 : -1; - } - } + function effect (_ref) { + var state = _ref.state, + instance = _ref.instance, + options = _ref.options; + var _options$scroll = options.scroll, + scroll = _options$scroll === void 0 ? true : _options$scroll, + _options$resize = options.resize, + resize = _options$resize === void 0 ? true : _options$resize; + var window = getWindow(state.elements.popper); + var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper); + + if ( scroll ) { + scrollParents.forEach(function (scrollParent) { + scrollParent.addEventListener('scroll', instance.update, passive); + }); + } - var commonStyles = Object.assign({ - position: position - }, adaptive && unsetSides); + if ( resize ) { + window.addEventListener('resize', instance.update, passive); + } - if (gpuAcceleration) { - var _Object$assign; + return function () { + if ( scroll ) { + scrollParents.forEach(function (scrollParent) { + scrollParent.removeEventListener('scroll', instance.update, passive); + }); + } - return Object.assign({}, commonStyles, (_Object$assign = {}, _Object$assign[sideY] = hasY ? '0' : '', _Object$assign[sideX] = hasX ? '0' : '', _Object$assign.transform = (win.devicePixelRatio || 1) <= 1 ? "translate(" + x + "px, " + y + "px)" : "translate3d(" + x + "px, " + y + "px, 0)", _Object$assign)); - } + if ( resize ) { + window.removeEventListener('resize', instance.update, passive); + } + }; + } // eslint-disable-next-line import/no-unused-modules - return Object.assign({}, commonStyles, (_Object$assign2 = {}, _Object$assign2[sideY] = hasY ? y + "px" : '', _Object$assign2[sideX] = hasX ? x + "px" : '', _Object$assign2.transform = '', _Object$assign2)); - } - - function computeStyles(_ref4) { - var state = _ref4.state, - options = _ref4.options; - var _options$gpuAccelerat = options.gpuAcceleration, - gpuAcceleration = _options$gpuAccelerat === void 0 ? true : _options$gpuAccelerat, - _options$adaptive = options.adaptive, - adaptive = _options$adaptive === void 0 ? true : _options$adaptive, - _options$roundOffsets = options.roundOffsets, - roundOffsets = _options$roundOffsets === void 0 ? true : _options$roundOffsets; - - var commonStyles = { - placement: getBasePlacement(state.placement), - variation: getVariation(state.placement), - popper: state.elements.popper, - popperRect: state.rects.popper, - gpuAcceleration: gpuAcceleration + const eventListeners = { + name: 'eventListeners', + enabled: true, + phase: 'write', + fn: function fn () { + }, + effect: effect, + data: {}, }; - if (state.modifiersData.popperOffsets != null) { - state.styles.popper = Object.assign({}, state.styles.popper, mapToStyles(Object.assign({}, commonStyles, { - offsets: state.modifiersData.popperOffsets, - position: state.options.strategy, - adaptive: adaptive, - roundOffsets: roundOffsets - }))); - } - - if (state.modifiersData.arrow != null) { - state.styles.arrow = Object.assign({}, state.styles.arrow, mapToStyles(Object.assign({}, commonStyles, { - offsets: state.modifiersData.arrow, - position: 'absolute', - adaptive: false, - roundOffsets: roundOffsets - }))); + var hash$1 = { + left: 'right', + right: 'left', + bottom: 'top', + top: 'bottom', + }; + function getOppositePlacement (placement) { + return placement.replace(/left|right|bottom|top/g, function (matched) { + return hash$1[matched]; + }); } - state.attributes.popper = Object.assign({}, state.attributes.popper, { - 'data-popper-placement': state.placement - }); - } // eslint-disable-next-line import/no-unused-modules - - - const computeStyles$1 = { - name: 'computeStyles', - enabled: true, - phase: 'beforeWrite', - fn: computeStyles, - data: {} - }; - - var passive = { - passive: true - }; - - function effect(_ref) { - var state = _ref.state, - instance = _ref.instance, - options = _ref.options; - var _options$scroll = options.scroll, - scroll = _options$scroll === void 0 ? true : _options$scroll, - _options$resize = options.resize, - resize = _options$resize === void 0 ? true : _options$resize; - var window = getWindow(state.elements.popper); - var scrollParents = [].concat(state.scrollParents.reference, state.scrollParents.popper); - - if (scroll) { - scrollParents.forEach(function (scrollParent) { - scrollParent.addEventListener('scroll', instance.update, passive); - }); + var hash = { + start: 'end', + end: 'start', + }; + function getOppositeVariationPlacement (placement) { + return placement.replace(/start|end/g, function (matched) { + return hash[matched]; + }); } - if (resize) { - window.addEventListener('resize', instance.update, passive); + function getWindowScroll (node) { + var win = getWindow(node); + var scrollLeft = win.pageXOffset; + var scrollTop = win.pageYOffset; + return { + scrollLeft: scrollLeft, + scrollTop: scrollTop, + }; } - return function () { - if (scroll) { - scrollParents.forEach(function (scrollParent) { - scrollParent.removeEventListener('scroll', instance.update, passive); - }); - } - - if (resize) { - window.removeEventListener('resize', instance.update, passive); - } - }; - } // eslint-disable-next-line import/no-unused-modules - - - const eventListeners = { - name: 'eventListeners', - enabled: true, - phase: 'write', - fn: function fn() {}, - effect: effect, - data: {} - }; - - var hash$1 = { - left: 'right', - right: 'left', - bottom: 'top', - top: 'bottom' - }; - function getOppositePlacement(placement) { - return placement.replace(/left|right|bottom|top/g, function (matched) { - return hash$1[matched]; - }); - } - - var hash = { - start: 'end', - end: 'start' - }; - function getOppositeVariationPlacement(placement) { - return placement.replace(/start|end/g, function (matched) { - return hash[matched]; - }); - } - - function getWindowScroll(node) { - var win = getWindow(node); - var scrollLeft = win.pageXOffset; - var scrollTop = win.pageYOffset; - return { - scrollLeft: scrollLeft, - scrollTop: scrollTop - }; - } - - function getWindowScrollBarX(element) { + function getWindowScrollBarX (element) { // If has a CSS width greater than the viewport, then this will be // incorrect for RTL. // Popper 1 is broken in this case and never had a bug report so let's assume @@ -2601,4212 +2564,4148 @@ // anyway. // Browsers where the left scrollbar doesn't cause an issue report `0` for // this (e.g. Edge 2019, IE11, Safari) - return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft; - } - - function getViewportRect(element) { - var win = getWindow(element); - var html = getDocumentElement(element); - var visualViewport = win.visualViewport; - var width = html.clientWidth; - var height = html.clientHeight; - var x = 0; - var y = 0; // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper - // can be obscured underneath it. - // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even - // if it isn't open, so if this isn't available, the popper will be detected - // to overflow the bottom of the screen too early. - - if (visualViewport) { - width = visualViewport.width; - height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently) - // In Chrome, it returns a value very close to 0 (+/-) but contains rounding - // errors due to floating point numbers, so we need to check precision. - // Safari returns a number <= 0, usually < -1 when pinch-zoomed - // Feature detection fails in mobile emulation mode in Chrome. - // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) < - // 0.001 - // Fallback here: "Not Safari" userAgent - - if (!/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) { - x = visualViewport.offsetLeft; - y = visualViewport.offsetTop; - } + return getBoundingClientRect(getDocumentElement(element)).left + getWindowScroll(element).scrollLeft; + } + + function getViewportRect (element) { + var win = getWindow(element); + var html = getDocumentElement(element); + var visualViewport = win.visualViewport; + var width = html.clientWidth; + var height = html.clientHeight; + var x = 0; + var y = 0; // NB: This isn't supported on iOS <= 12. If the keyboard is open, the popper + // can be obscured underneath it. + // Also, `html.clientHeight` adds the bottom bar height in Safari iOS, even + // if it isn't open, so if this isn't available, the popper will be detected + // to overflow the bottom of the screen too early. + + if ( visualViewport ) { + width = visualViewport.width; + height = visualViewport.height; // Uses Layout Viewport (like Chrome; Safari does not currently) + // In Chrome, it returns a value very close to 0 (+/-) but contains rounding + // errors due to floating point numbers, so we need to check precision. + // Safari returns a number <= 0, usually < -1 when pinch-zoomed + // Feature detection fails in mobile emulation mode in Chrome. + // Math.abs(win.innerWidth / visualViewport.scale - visualViewport.width) < + // 0.001 + // Fallback here: "Not Safari" userAgent + + if ( ! /^((?!chrome|android).)*safari/i.test(navigator.userAgent) ) { + x = visualViewport.offsetLeft; + y = visualViewport.offsetTop; + } + } + + return { + width: width, + height: height, + x: x + getWindowScrollBarX(element), + y: y, + }; } - return { - width: width, - height: height, - x: x + getWindowScrollBarX(element), - y: y - }; - } + // of the `` and `` rect bounds if horizontally scrollable - // of the `` and `` rect bounds if horizontally scrollable + function getDocumentRect (element) { + var _element$ownerDocumen; - function getDocumentRect(element) { - var _element$ownerDocumen; + var html = getDocumentElement(element); + var winScroll = getWindowScroll(element); + var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body; + var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0); + var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0); + var x = -winScroll.scrollLeft + getWindowScrollBarX(element); + var y = -winScroll.scrollTop; - var html = getDocumentElement(element); - var winScroll = getWindowScroll(element); - var body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body; - var width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0); - var height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0); - var x = -winScroll.scrollLeft + getWindowScrollBarX(element); - var y = -winScroll.scrollTop; + if ( getComputedStyle$1(body || html).direction === 'rtl' ) { + x += max(html.clientWidth, body ? body.clientWidth : 0) - width; + } - if (getComputedStyle$1(body || html).direction === 'rtl') { - x += max(html.clientWidth, body ? body.clientWidth : 0) - width; + return { + width: width, + height: height, + x: x, + y: y, + }; } - return { - width: width, - height: height, - x: x, - y: y - }; - } - - function isScrollParent(element) { + function isScrollParent (element) { // Firefox wants us to check `-x` and `-y` variations as well - var _getComputedStyle = getComputedStyle$1(element), - overflow = _getComputedStyle.overflow, - overflowX = _getComputedStyle.overflowX, - overflowY = _getComputedStyle.overflowY; - - return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX); - } - - function getScrollParent(node) { - if (['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0) { - // $FlowFixMe[incompatible-return]: assume body is always available - return node.ownerDocument.body; - } + var _getComputedStyle = getComputedStyle$1(element), + overflow = _getComputedStyle.overflow, + overflowX = _getComputedStyle.overflowX, + overflowY = _getComputedStyle.overflowY; - if (isHTMLElement(node) && isScrollParent(node)) { - return node; + return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX); } - return getScrollParent(getParentNode(node)); - } + function getScrollParent (node) { + if ( ['html', 'body', '#document'].indexOf(getNodeName(node)) >= 0 ) { + // $FlowFixMe[incompatible-return]: assume body is always available + return node.ownerDocument.body; + } - /* + if ( isHTMLElement(node) && isScrollParent(node) ) { + return node; + } + + return getScrollParent(getParentNode(node)); + } + + /* given a DOM element, return the list of all scroll parents, up the list of ancesors until we get to the top window object. This list is what we attach scroll listeners to, because if any of these parent elements scroll, we'll need to re-calculate the reference element's position. */ - function listScrollParents(element, list) { - var _element$ownerDocumen; + function listScrollParents (element, list) { + var _element$ownerDocumen; - if (list === void 0) { - list = []; + if ( list === void 0 ) { + list = []; + } + + var scrollParent = getScrollParent(element); + var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body); + var win = getWindow(scrollParent); + var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent; + var updatedList = list.concat(target); + return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here + updatedList.concat(listScrollParents(getParentNode(target))); + } + + function rectToClientRect (rect) { + return Object.assign({}, rect, { + left: rect.x, + top: rect.y, + right: rect.x + rect.width, + bottom: rect.y + rect.height, + }); } - var scrollParent = getScrollParent(element); - var isBody = scrollParent === ((_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body); - var win = getWindow(scrollParent); - var target = isBody ? [win].concat(win.visualViewport || [], isScrollParent(scrollParent) ? scrollParent : []) : scrollParent; - var updatedList = list.concat(target); - return isBody ? updatedList : // $FlowFixMe[incompatible-call]: isBody tells us target will be an HTMLElement here - updatedList.concat(listScrollParents(getParentNode(target))); - } - - function rectToClientRect(rect) { - return Object.assign({}, rect, { - left: rect.x, - top: rect.y, - right: rect.x + rect.width, - bottom: rect.y + rect.height - }); - } - - function getInnerBoundingClientRect(element) { - var rect = getBoundingClientRect(element); - rect.top = rect.top + element.clientTop; - rect.left = rect.left + element.clientLeft; - rect.bottom = rect.top + element.clientHeight; - rect.right = rect.left + element.clientWidth; - rect.width = element.clientWidth; - rect.height = element.clientHeight; - rect.x = rect.left; - rect.y = rect.top; - return rect; - } - - function getClientRectFromMixedType(element, clippingParent) { - return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isHTMLElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element))); - } // A "clipping parent" is an overflowable container with the characteristic of - // clipping (or hiding) overflowing elements with a position different from - // `initial` - - - function getClippingParents(element) { - var clippingParents = listScrollParents(getParentNode(element)); - var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle$1(element).position) >= 0; - var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element; - - if (!isElement(clipperElement)) { - return []; - } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414 - - - return clippingParents.filter(function (clippingParent) { - return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body'; - }); - } // Gets the maximum area that the element is visible in due to any number of - // clipping parents - - - function getClippingRect(element, boundary, rootBoundary) { - var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary); - var clippingParents = [].concat(mainClippingParents, [rootBoundary]); - var firstClippingParent = clippingParents[0]; - var clippingRect = clippingParents.reduce(function (accRect, clippingParent) { - var rect = getClientRectFromMixedType(element, clippingParent); - accRect.top = max(rect.top, accRect.top); - accRect.right = min(rect.right, accRect.right); - accRect.bottom = min(rect.bottom, accRect.bottom); - accRect.left = max(rect.left, accRect.left); - return accRect; - }, getClientRectFromMixedType(element, firstClippingParent)); - clippingRect.width = clippingRect.right - clippingRect.left; - clippingRect.height = clippingRect.bottom - clippingRect.top; - clippingRect.x = clippingRect.left; - clippingRect.y = clippingRect.top; - return clippingRect; - } - - function computeOffsets(_ref) { - var reference = _ref.reference, - element = _ref.element, - placement = _ref.placement; - var basePlacement = placement ? getBasePlacement(placement) : null; - var variation = placement ? getVariation(placement) : null; - var commonX = reference.x + reference.width / 2 - element.width / 2; - var commonY = reference.y + reference.height / 2 - element.height / 2; - var offsets; - - switch (basePlacement) { - case top: - offsets = { - x: commonX, - y: reference.y - element.height - }; - break; + function getInnerBoundingClientRect (element) { + var rect = getBoundingClientRect(element); + rect.top = rect.top + element.clientTop; + rect.left = rect.left + element.clientLeft; + rect.bottom = rect.top + element.clientHeight; + rect.right = rect.left + element.clientWidth; + rect.width = element.clientWidth; + rect.height = element.clientHeight; + rect.x = rect.left; + rect.y = rect.top; + return rect; + } + + function getClientRectFromMixedType (element, clippingParent) { + return clippingParent === viewport ? rectToClientRect(getViewportRect(element)) : isHTMLElement(clippingParent) ? getInnerBoundingClientRect(clippingParent) : rectToClientRect(getDocumentRect(getDocumentElement(element))); + } // A "clipping parent" is an overflowable container with the characteristic of + // clipping (or hiding) overflowing elements with a position different from + // `initial` + + function getClippingParents (element) { + var clippingParents = listScrollParents(getParentNode(element)); + var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle$1(element).position) >= 0; + var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element; + + if ( ! isElement(clipperElement) ) { + return []; + } // $FlowFixMe[incompatible-return]: https://github.com/facebook/flow/issues/1414 + + return clippingParents.filter(function (clippingParent) { + return isElement(clippingParent) && contains(clippingParent, clipperElement) && getNodeName(clippingParent) !== 'body'; + }); + } // Gets the maximum area that the element is visible in due to any number of + // clipping parents + + function getClippingRect (element, boundary, rootBoundary) { + var mainClippingParents = boundary === 'clippingParents' ? getClippingParents(element) : [].concat(boundary); + var clippingParents = [].concat(mainClippingParents, [rootBoundary]); + var firstClippingParent = clippingParents[0]; + var clippingRect = clippingParents.reduce(function (accRect, clippingParent) { + var rect = getClientRectFromMixedType(element, clippingParent); + accRect.top = max(rect.top, accRect.top); + accRect.right = min(rect.right, accRect.right); + accRect.bottom = min(rect.bottom, accRect.bottom); + accRect.left = max(rect.left, accRect.left); + return accRect; + }, getClientRectFromMixedType(element, firstClippingParent)); + clippingRect.width = clippingRect.right - clippingRect.left; + clippingRect.height = clippingRect.bottom - clippingRect.top; + clippingRect.x = clippingRect.left; + clippingRect.y = clippingRect.top; + return clippingRect; + } + + function computeOffsets (_ref) { + var reference = _ref.reference, + element = _ref.element, + placement = _ref.placement; + var basePlacement = placement ? getBasePlacement(placement) : null; + var variation = placement ? getVariation(placement) : null; + var commonX = reference.x + reference.width / 2 - element.width / 2; + var commonY = reference.y + reference.height / 2 - element.height / 2; + var offsets; + + switch ( basePlacement ) { + case top: + offsets = { + x: commonX, + y: reference.y - element.height, + }; + break; - case bottom: - offsets = { - x: commonX, - y: reference.y + reference.height - }; - break; + case bottom: + offsets = { + x: commonX, + y: reference.y + reference.height, + }; + break; - case right: - offsets = { - x: reference.x + reference.width, - y: commonY - }; - break; + case right: + offsets = { + x: reference.x + reference.width, + y: commonY, + }; + break; - case left: - offsets = { - x: reference.x - element.width, - y: commonY - }; - break; + case left: + offsets = { + x: reference.x - element.width, + y: commonY, + }; + break; - default: - offsets = { - x: reference.x, - y: reference.y - }; - } + default: + offsets = { + x: reference.x, + y: reference.y, + }; + } - var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null; + var mainAxis = basePlacement ? getMainAxisFromPlacement(basePlacement) : null; - if (mainAxis != null) { - var len = mainAxis === 'y' ? 'height' : 'width'; + if ( mainAxis != null ) { + var len = mainAxis === 'y' ? 'height' : 'width'; - switch (variation) { - case start: - offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2); - break; + switch ( variation ) { + case start: + offsets[mainAxis] = offsets[mainAxis] - (reference[len] / 2 - element[len] / 2); + break; - case end: - offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2); - break; - } + case end: + offsets[mainAxis] = offsets[mainAxis] + (reference[len] / 2 - element[len] / 2); + break; + } + } + + return offsets; } - return offsets; - } + function detectOverflow (state, options) { + if ( options === void 0 ) { + options = {}; + } - function detectOverflow(state, options) { - if (options === void 0) { - options = {}; + var _options = options, + _options$placement = _options.placement, + placement = _options$placement === void 0 ? state.placement : _options$placement, + _options$boundary = _options.boundary, + boundary = _options$boundary === void 0 ? clippingParents : _options$boundary, + _options$rootBoundary = _options.rootBoundary, + rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary, + _options$elementConte = _options.elementContext, + elementContext = _options$elementConte === void 0 ? popper : _options$elementConte, + _options$altBoundary = _options.altBoundary, + altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary, + _options$padding = _options.padding, + padding = _options$padding === void 0 ? 0 : _options$padding; + var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); + var altContext = elementContext === popper ? reference : popper; + var popperRect = state.rects.popper; + var element = state.elements[altBoundary ? altContext : elementContext]; + var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary); + var referenceClientRect = getBoundingClientRect(state.elements.reference); + var popperOffsets = computeOffsets({ + reference: referenceClientRect, + element: popperRect, + strategy: 'absolute', + placement: placement, + }); + var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); + var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect + // 0 or negative = within the clipping rect + + var overflowOffsets = { + top: clippingClientRect.top - elementClientRect.top + paddingObject.top, + bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom, + left: clippingClientRect.left - elementClientRect.left + paddingObject.left, + right: elementClientRect.right - clippingClientRect.right + paddingObject.right, + }; + var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element + + if ( elementContext === popper && offsetData ) { + var offset = offsetData[placement]; + Object.keys(overflowOffsets).forEach(function (key) { + var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1; + var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x'; + overflowOffsets[key] += offset[axis] * multiply; + }); + } + + return overflowOffsets; } - var _options = options, - _options$placement = _options.placement, - placement = _options$placement === void 0 ? state.placement : _options$placement, - _options$boundary = _options.boundary, - boundary = _options$boundary === void 0 ? clippingParents : _options$boundary, - _options$rootBoundary = _options.rootBoundary, - rootBoundary = _options$rootBoundary === void 0 ? viewport : _options$rootBoundary, - _options$elementConte = _options.elementContext, - elementContext = _options$elementConte === void 0 ? popper : _options$elementConte, - _options$altBoundary = _options.altBoundary, - altBoundary = _options$altBoundary === void 0 ? false : _options$altBoundary, - _options$padding = _options.padding, - padding = _options$padding === void 0 ? 0 : _options$padding; - var paddingObject = mergePaddingObject(typeof padding !== 'number' ? padding : expandToHashMap(padding, basePlacements)); - var altContext = elementContext === popper ? reference : popper; - var popperRect = state.rects.popper; - var element = state.elements[altBoundary ? altContext : elementContext]; - var clippingClientRect = getClippingRect(isElement(element) ? element : element.contextElement || getDocumentElement(state.elements.popper), boundary, rootBoundary); - var referenceClientRect = getBoundingClientRect(state.elements.reference); - var popperOffsets = computeOffsets({ - reference: referenceClientRect, - element: popperRect, - strategy: 'absolute', - placement: placement - }); - var popperClientRect = rectToClientRect(Object.assign({}, popperRect, popperOffsets)); - var elementClientRect = elementContext === popper ? popperClientRect : referenceClientRect; // positive = overflowing the clipping rect - // 0 or negative = within the clipping rect - - var overflowOffsets = { - top: clippingClientRect.top - elementClientRect.top + paddingObject.top, - bottom: elementClientRect.bottom - clippingClientRect.bottom + paddingObject.bottom, - left: clippingClientRect.left - elementClientRect.left + paddingObject.left, - right: elementClientRect.right - clippingClientRect.right + paddingObject.right - }; - var offsetData = state.modifiersData.offset; // Offsets can be applied only to the popper element - - if (elementContext === popper && offsetData) { - var offset = offsetData[placement]; - Object.keys(overflowOffsets).forEach(function (key) { - var multiply = [right, bottom].indexOf(key) >= 0 ? 1 : -1; - var axis = [top, bottom].indexOf(key) >= 0 ? 'y' : 'x'; - overflowOffsets[key] += offset[axis] * multiply; - }); + function computeAutoPlacement (state, options) { + if ( options === void 0 ) { + options = {}; + } + + var _options = options, + placement = _options.placement, + boundary = _options.boundary, + rootBoundary = _options.rootBoundary, + padding = _options.padding, + flipVariations = _options.flipVariations, + _options$allowedAutoP = _options.allowedAutoPlacements, + allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP; + var variation = getVariation(placement); + var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) { + return getVariation(placement) === variation; + }) : basePlacements; + var allowedPlacements = placements$1.filter(function (placement) { + return allowedAutoPlacements.indexOf(placement) >= 0; + }); + + if ( allowedPlacements.length === 0 ) { + allowedPlacements = placements$1; + } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... + + var overflows = allowedPlacements.reduce(function (acc, placement) { + acc[placement] = detectOverflow(state, { + placement: placement, + boundary: boundary, + rootBoundary: rootBoundary, + padding: padding, + })[getBasePlacement(placement)]; + return acc; + }, {}); + return Object.keys(overflows).sort(function (a, b) { + return overflows[a] - overflows[b]; + }); } - return overflowOffsets; - } + function getExpandedFallbackPlacements (placement) { + if ( getBasePlacement(placement) === auto ) { + return []; + } - function computeAutoPlacement(state, options) { - if (options === void 0) { - options = {}; + var oppositePlacement = getOppositePlacement(placement); + return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)]; } - var _options = options, - placement = _options.placement, - boundary = _options.boundary, - rootBoundary = _options.rootBoundary, - padding = _options.padding, - flipVariations = _options.flipVariations, - _options$allowedAutoP = _options.allowedAutoPlacements, - allowedAutoPlacements = _options$allowedAutoP === void 0 ? placements : _options$allowedAutoP; - var variation = getVariation(placement); - var placements$1 = variation ? flipVariations ? variationPlacements : variationPlacements.filter(function (placement) { - return getVariation(placement) === variation; - }) : basePlacements; - var allowedPlacements = placements$1.filter(function (placement) { - return allowedAutoPlacements.indexOf(placement) >= 0; - }); + function flip (_ref) { + var state = _ref.state, + options = _ref.options, + name = _ref.name; - if (allowedPlacements.length === 0) { - allowedPlacements = placements$1; - } // $FlowFixMe[incompatible-type]: Flow seems to have problems with two array unions... - - - var overflows = allowedPlacements.reduce(function (acc, placement) { - acc[placement] = detectOverflow(state, { - placement: placement, - boundary: boundary, - rootBoundary: rootBoundary, - padding: padding - })[getBasePlacement(placement)]; - return acc; - }, {}); - return Object.keys(overflows).sort(function (a, b) { - return overflows[a] - overflows[b]; - }); - } + if ( state.modifiersData[name]._skip ) { + return; + } - function getExpandedFallbackPlacements(placement) { - if (getBasePlacement(placement) === auto) { - return []; - } + var _options$mainAxis = options.mainAxis, + checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, + _options$altAxis = options.altAxis, + checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis, + specifiedFallbackPlacements = options.fallbackPlacements, + padding = options.padding, + boundary = options.boundary, + rootBoundary = options.rootBoundary, + altBoundary = options.altBoundary, + _options$flipVariatio = options.flipVariations, + flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio, + allowedAutoPlacements = options.allowedAutoPlacements; + var preferredPlacement = state.options.placement; + var basePlacement = getBasePlacement(preferredPlacement); + var isBasePlacement = basePlacement === preferredPlacement; + var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement)); + var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) { + return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, { + placement: placement, + boundary: boundary, + rootBoundary: rootBoundary, + padding: padding, + flipVariations: flipVariations, + allowedAutoPlacements: allowedAutoPlacements, + }) : placement); + }, []); + var referenceRect = state.rects.reference; + var popperRect = state.rects.popper; + var checksMap = new Map(); + var makeFallbackChecks = true; + var firstFittingPlacement = placements[0]; + + for ( var i = 0; i < placements.length; i++ ) { + var placement = placements[i]; + + var _basePlacement = getBasePlacement(placement); + + var isStartVariation = getVariation(placement) === start; + var isVertical = [top, bottom].indexOf(_basePlacement) >= 0; + var len = isVertical ? 'width' : 'height'; + var overflow = detectOverflow(state, { + placement: placement, + boundary: boundary, + rootBoundary: rootBoundary, + altBoundary: altBoundary, + padding: padding, + }); + var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top; - var oppositePlacement = getOppositePlacement(placement); - return [getOppositeVariationPlacement(placement), oppositePlacement, getOppositeVariationPlacement(oppositePlacement)]; - } + if ( referenceRect[len] > popperRect[len] ) { + mainVariationSide = getOppositePlacement(mainVariationSide); + } - function flip(_ref) { - var state = _ref.state, - options = _ref.options, - name = _ref.name; + var altVariationSide = getOppositePlacement(mainVariationSide); + var checks = []; - if (state.modifiersData[name]._skip) { - return; - } + if ( checkMainAxis ) { + checks.push(overflow[_basePlacement] <= 0); + } - var _options$mainAxis = options.mainAxis, - checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, - _options$altAxis = options.altAxis, - checkAltAxis = _options$altAxis === void 0 ? true : _options$altAxis, - specifiedFallbackPlacements = options.fallbackPlacements, - padding = options.padding, - boundary = options.boundary, - rootBoundary = options.rootBoundary, - altBoundary = options.altBoundary, - _options$flipVariatio = options.flipVariations, - flipVariations = _options$flipVariatio === void 0 ? true : _options$flipVariatio, - allowedAutoPlacements = options.allowedAutoPlacements; - var preferredPlacement = state.options.placement; - var basePlacement = getBasePlacement(preferredPlacement); - var isBasePlacement = basePlacement === preferredPlacement; - var fallbackPlacements = specifiedFallbackPlacements || (isBasePlacement || !flipVariations ? [getOppositePlacement(preferredPlacement)] : getExpandedFallbackPlacements(preferredPlacement)); - var placements = [preferredPlacement].concat(fallbackPlacements).reduce(function (acc, placement) { - return acc.concat(getBasePlacement(placement) === auto ? computeAutoPlacement(state, { - placement: placement, - boundary: boundary, - rootBoundary: rootBoundary, - padding: padding, - flipVariations: flipVariations, - allowedAutoPlacements: allowedAutoPlacements - }) : placement); - }, []); - var referenceRect = state.rects.reference; - var popperRect = state.rects.popper; - var checksMap = new Map(); - var makeFallbackChecks = true; - var firstFittingPlacement = placements[0]; - - for (var i = 0; i < placements.length; i++) { - var placement = placements[i]; - - var _basePlacement = getBasePlacement(placement); - - var isStartVariation = getVariation(placement) === start; - var isVertical = [top, bottom].indexOf(_basePlacement) >= 0; - var len = isVertical ? 'width' : 'height'; - var overflow = detectOverflow(state, { - placement: placement, - boundary: boundary, - rootBoundary: rootBoundary, - altBoundary: altBoundary, - padding: padding - }); - var mainVariationSide = isVertical ? isStartVariation ? right : left : isStartVariation ? bottom : top; - - if (referenceRect[len] > popperRect[len]) { - mainVariationSide = getOppositePlacement(mainVariationSide); - } - - var altVariationSide = getOppositePlacement(mainVariationSide); - var checks = []; - - if (checkMainAxis) { - checks.push(overflow[_basePlacement] <= 0); - } - - if (checkAltAxis) { - checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0); - } - - if (checks.every(function (check) { - return check; - })) { - firstFittingPlacement = placement; - makeFallbackChecks = false; - break; - } - - checksMap.set(placement, checks); - } + if ( checkAltAxis ) { + checks.push(overflow[mainVariationSide] <= 0, overflow[altVariationSide] <= 0); + } - if (makeFallbackChecks) { - // `2` may be desired in some cases – research later - var numberOfChecks = flipVariations ? 3 : 1; + if ( checks.every(function (check) { + return check; + }) ) { + firstFittingPlacement = placement; + makeFallbackChecks = false; + break; + } - var _loop = function _loop(_i) { - var fittingPlacement = placements.find(function (placement) { - var checks = checksMap.get(placement); + checksMap.set(placement, checks); + } - if (checks) { - return checks.slice(0, _i).every(function (check) { - return check; - }); - } - }); + if ( makeFallbackChecks ) { + // `2` may be desired in some cases – research later + var numberOfChecks = flipVariations ? 3 : 1; + + var _loop = function _loop (_i) { + var fittingPlacement = placements.find(function (placement) { + var checks = checksMap.get(placement); - if (fittingPlacement) { - firstFittingPlacement = fittingPlacement; - return "break"; + if ( checks ) { + return checks.slice(0, _i).every(function (check) { + return check; + }); + } + }); + + if ( fittingPlacement ) { + firstFittingPlacement = fittingPlacement; + return 'break'; + } + }; + + for ( var _i = numberOfChecks; _i > 0; _i-- ) { + var _ret = _loop(_i); + + if ( _ret === 'break' ) break; + } } - }; - for (var _i = numberOfChecks; _i > 0; _i--) { - var _ret = _loop(_i); + if ( state.placement !== firstFittingPlacement ) { + state.modifiersData[name]._skip = true; + state.placement = firstFittingPlacement; + state.reset = true; + } + } // eslint-disable-next-line import/no-unused-modules + + const flip$1 = { + name: 'flip', + enabled: true, + phase: 'main', + fn: flip, + requiresIfExists: ['offset'], + data: { + _skip: false, + }, + }; - if (_ret === "break") break; - } + function getSideOffsets (overflow, rect, preventedOffsets) { + if ( preventedOffsets === void 0 ) { + preventedOffsets = { + x: 0, + y: 0, + }; + } + + return { + top: overflow.top - rect.height - preventedOffsets.y, + right: overflow.right - rect.width + preventedOffsets.x, + bottom: overflow.bottom - rect.height + preventedOffsets.y, + left: overflow.left - rect.width - preventedOffsets.x, + }; } - if (state.placement !== firstFittingPlacement) { - state.modifiersData[name]._skip = true; - state.placement = firstFittingPlacement; - state.reset = true; + function isAnySideFullyClipped (overflow) { + return [top, right, bottom, left].some(function (side) { + return overflow[side] >= 0; + }); } - } // eslint-disable-next-line import/no-unused-modules + function hide (_ref) { + var state = _ref.state, + name = _ref.name; + var referenceRect = state.rects.reference; + var popperRect = state.rects.popper; + var preventedOffsets = state.modifiersData.preventOverflow; + var referenceOverflow = detectOverflow(state, { + elementContext: 'reference', + }); + var popperAltOverflow = detectOverflow(state, { + altBoundary: true, + }); + var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect); + var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets); + var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets); + var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets); + state.modifiersData[name] = { + referenceClippingOffsets: referenceClippingOffsets, + popperEscapeOffsets: popperEscapeOffsets, + isReferenceHidden: isReferenceHidden, + hasPopperEscaped: hasPopperEscaped, + }; + state.attributes.popper = Object.assign({}, state.attributes.popper, { + 'data-popper-reference-hidden': isReferenceHidden, + 'data-popper-escaped': hasPopperEscaped, + }); + } // eslint-disable-next-line import/no-unused-modules + + const hide$1 = { + name: 'hide', + enabled: true, + phase: 'main', + requiresIfExists: ['preventOverflow'], + fn: hide, + }; - const flip$1 = { - name: 'flip', - enabled: true, - phase: 'main', - fn: flip, - requiresIfExists: ['offset'], - data: { - _skip: false - } - }; - - function getSideOffsets(overflow, rect, preventedOffsets) { - if (preventedOffsets === void 0) { - preventedOffsets = { - x: 0, - y: 0 - }; + function distanceAndSkiddingToXY (placement, rects, offset) { + var basePlacement = getBasePlacement(placement); + var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1; + + var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, { + placement: placement, + })) : offset, + skidding = _ref[0], + distance = _ref[1]; + + skidding = skidding || 0; + distance = (distance || 0) * invertDistance; + return [left, right].indexOf(basePlacement) >= 0 ? { + x: distance, + y: skidding, + } : { + x: skidding, + y: distance, + }; } - return { - top: overflow.top - rect.height - preventedOffsets.y, - right: overflow.right - rect.width + preventedOffsets.x, - bottom: overflow.bottom - rect.height + preventedOffsets.y, - left: overflow.left - rect.width - preventedOffsets.x - }; - } + function offset (_ref2) { + var state = _ref2.state, + options = _ref2.options, + name = _ref2.name; + var _options$offset = options.offset, + offset = _options$offset === void 0 ? [0, 0] : _options$offset; + var data = placements.reduce(function (acc, placement) { + acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset); + return acc; + }, {}); + var _data$state$placement = data[state.placement], + x = _data$state$placement.x, + y = _data$state$placement.y; + + if ( state.modifiersData.popperOffsets != null ) { + state.modifiersData.popperOffsets.x += x; + state.modifiersData.popperOffsets.y += y; + } - function isAnySideFullyClipped(overflow) { - return [top, right, bottom, left].some(function (side) { - return overflow[side] >= 0; - }); - } - - function hide(_ref) { - var state = _ref.state, - name = _ref.name; - var referenceRect = state.rects.reference; - var popperRect = state.rects.popper; - var preventedOffsets = state.modifiersData.preventOverflow; - var referenceOverflow = detectOverflow(state, { - elementContext: 'reference' - }); - var popperAltOverflow = detectOverflow(state, { - altBoundary: true - }); - var referenceClippingOffsets = getSideOffsets(referenceOverflow, referenceRect); - var popperEscapeOffsets = getSideOffsets(popperAltOverflow, popperRect, preventedOffsets); - var isReferenceHidden = isAnySideFullyClipped(referenceClippingOffsets); - var hasPopperEscaped = isAnySideFullyClipped(popperEscapeOffsets); - state.modifiersData[name] = { - referenceClippingOffsets: referenceClippingOffsets, - popperEscapeOffsets: popperEscapeOffsets, - isReferenceHidden: isReferenceHidden, - hasPopperEscaped: hasPopperEscaped - }; - state.attributes.popper = Object.assign({}, state.attributes.popper, { - 'data-popper-reference-hidden': isReferenceHidden, - 'data-popper-escaped': hasPopperEscaped - }); - } // eslint-disable-next-line import/no-unused-modules - - - const hide$1 = { - name: 'hide', - enabled: true, - phase: 'main', - requiresIfExists: ['preventOverflow'], - fn: hide - }; - - function distanceAndSkiddingToXY(placement, rects, offset) { - var basePlacement = getBasePlacement(placement); - var invertDistance = [left, top].indexOf(basePlacement) >= 0 ? -1 : 1; - - var _ref = typeof offset === 'function' ? offset(Object.assign({}, rects, { - placement: placement - })) : offset, - skidding = _ref[0], - distance = _ref[1]; - - skidding = skidding || 0; - distance = (distance || 0) * invertDistance; - return [left, right].indexOf(basePlacement) >= 0 ? { - x: distance, - y: skidding - } : { - x: skidding, - y: distance + state.modifiersData[name] = data; + } // eslint-disable-next-line import/no-unused-modules + + const offset$1 = { + name: 'offset', + enabled: true, + phase: 'main', + requires: ['popperOffsets'], + fn: offset, }; - } - - function offset(_ref2) { - var state = _ref2.state, - options = _ref2.options, - name = _ref2.name; - var _options$offset = options.offset, - offset = _options$offset === void 0 ? [0, 0] : _options$offset; - var data = placements.reduce(function (acc, placement) { - acc[placement] = distanceAndSkiddingToXY(placement, state.rects, offset); - return acc; - }, {}); - var _data$state$placement = data[state.placement], - x = _data$state$placement.x, - y = _data$state$placement.y; - - if (state.modifiersData.popperOffsets != null) { - state.modifiersData.popperOffsets.x += x; - state.modifiersData.popperOffsets.y += y; - } - state.modifiersData[name] = data; - } // eslint-disable-next-line import/no-unused-modules - - - const offset$1 = { - name: 'offset', - enabled: true, - phase: 'main', - requires: ['popperOffsets'], - fn: offset - }; - - function popperOffsets(_ref) { - var state = _ref.state, - name = _ref.name; - // Offsets are the actual position the popper needs to have to be - // properly positioned near its reference element - // This is the most basic placement, and will be adjusted by - // the modifiers in the next step - state.modifiersData[name] = computeOffsets({ - reference: state.rects.reference, - element: state.rects.popper, - strategy: 'absolute', - placement: state.placement - }); - } // eslint-disable-next-line import/no-unused-modules - - - const popperOffsets$1 = { - name: 'popperOffsets', - enabled: true, - phase: 'read', - fn: popperOffsets, - data: {} - }; - - function getAltAxis(axis) { - return axis === 'x' ? 'y' : 'x'; - } - - function preventOverflow(_ref) { - var state = _ref.state, - options = _ref.options, - name = _ref.name; - var _options$mainAxis = options.mainAxis, - checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, - _options$altAxis = options.altAxis, - checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis, - boundary = options.boundary, - rootBoundary = options.rootBoundary, - altBoundary = options.altBoundary, - padding = options.padding, - _options$tether = options.tether, - tether = _options$tether === void 0 ? true : _options$tether, - _options$tetherOffset = options.tetherOffset, - tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset; - var overflow = detectOverflow(state, { - boundary: boundary, - rootBoundary: rootBoundary, - padding: padding, - altBoundary: altBoundary - }); - var basePlacement = getBasePlacement(state.placement); - var variation = getVariation(state.placement); - var isBasePlacement = !variation; - var mainAxis = getMainAxisFromPlacement(basePlacement); - var altAxis = getAltAxis(mainAxis); - var popperOffsets = state.modifiersData.popperOffsets; - var referenceRect = state.rects.reference; - var popperRect = state.rects.popper; - var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, { - placement: state.placement - })) : tetherOffset; - var data = { - x: 0, - y: 0 + function popperOffsets (_ref) { + var state = _ref.state, + name = _ref.name; + // Offsets are the actual position the popper needs to have to be + // properly positioned near its reference element + // This is the most basic placement, and will be adjusted by + // the modifiers in the next step + state.modifiersData[name] = computeOffsets({ + reference: state.rects.reference, + element: state.rects.popper, + strategy: 'absolute', + placement: state.placement, + }); + } // eslint-disable-next-line import/no-unused-modules + + const popperOffsets$1 = { + name: 'popperOffsets', + enabled: true, + phase: 'read', + fn: popperOffsets, + data: {}, }; - if (!popperOffsets) { - return; - } + function getAltAxis (axis) { + return axis === 'x' ? 'y' : 'x'; + } + + function preventOverflow (_ref) { + var state = _ref.state, + options = _ref.options, + name = _ref.name; + var _options$mainAxis = options.mainAxis, + checkMainAxis = _options$mainAxis === void 0 ? true : _options$mainAxis, + _options$altAxis = options.altAxis, + checkAltAxis = _options$altAxis === void 0 ? false : _options$altAxis, + boundary = options.boundary, + rootBoundary = options.rootBoundary, + altBoundary = options.altBoundary, + padding = options.padding, + _options$tether = options.tether, + tether = _options$tether === void 0 ? true : _options$tether, + _options$tetherOffset = options.tetherOffset, + tetherOffset = _options$tetherOffset === void 0 ? 0 : _options$tetherOffset; + var overflow = detectOverflow(state, { + boundary: boundary, + rootBoundary: rootBoundary, + padding: padding, + altBoundary: altBoundary, + }); + var basePlacement = getBasePlacement(state.placement); + var variation = getVariation(state.placement); + var isBasePlacement = !variation; + var mainAxis = getMainAxisFromPlacement(basePlacement); + var altAxis = getAltAxis(mainAxis); + var popperOffsets = state.modifiersData.popperOffsets; + var referenceRect = state.rects.reference; + var popperRect = state.rects.popper; + var tetherOffsetValue = typeof tetherOffset === 'function' ? tetherOffset(Object.assign({}, state.rects, { + placement: state.placement, + })) : tetherOffset; + var data = { + x: 0, + y: 0, + }; - if (checkMainAxis || checkAltAxis) { - var mainSide = mainAxis === 'y' ? top : left; - var altSide = mainAxis === 'y' ? bottom : right; - var len = mainAxis === 'y' ? 'height' : 'width'; - var offset = popperOffsets[mainAxis]; - var min$1 = popperOffsets[mainAxis] + overflow[mainSide]; - var max$1 = popperOffsets[mainAxis] - overflow[altSide]; - var additive = tether ? -popperRect[len] / 2 : 0; - var minLen = variation === start ? referenceRect[len] : popperRect[len]; - var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go - // outside the reference bounds - - var arrowElement = state.elements.arrow; - var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : { - width: 0, - height: 0 - }; - var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject(); - var arrowPaddingMin = arrowPaddingObject[mainSide]; - var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want - // to include its full size in the calculation. If the reference is small - // and near the edge of a boundary, the popper can overflow even if the - // reference is not overflowing as well (e.g. virtual elements with no - // width or height) - - var arrowLen = within(0, referenceRect[len], arrowRect[len]); - var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - tetherOffsetValue : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue; - var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + tetherOffsetValue : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue; - var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow); - var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0; - var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][mainAxis] : 0; - var tetherMin = popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset; - var tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue; - - if (checkMainAxis) { - var preventedOffset = within(tether ? min(min$1, tetherMin) : min$1, offset, tether ? max(max$1, tetherMax) : max$1); - popperOffsets[mainAxis] = preventedOffset; - data[mainAxis] = preventedOffset - offset; - } - - if (checkAltAxis) { - var _mainSide = mainAxis === 'x' ? top : left; - - var _altSide = mainAxis === 'x' ? bottom : right; - - var _offset = popperOffsets[altAxis]; - - var _min = _offset + overflow[_mainSide]; - - var _max = _offset - overflow[_altSide]; - - var _preventedOffset = within(tether ? min(_min, tetherMin) : _min, _offset, tether ? max(_max, tetherMax) : _max); - - popperOffsets[altAxis] = _preventedOffset; - data[altAxis] = _preventedOffset - _offset; - } - } + if ( ! popperOffsets ) { + return; + } - state.modifiersData[name] = data; - } // eslint-disable-next-line import/no-unused-modules + if ( checkMainAxis || checkAltAxis ) { + var mainSide = mainAxis === 'y' ? top : left; + var altSide = mainAxis === 'y' ? bottom : right; + var len = mainAxis === 'y' ? 'height' : 'width'; + var offset = popperOffsets[mainAxis]; + var min$1 = popperOffsets[mainAxis] + overflow[mainSide]; + var max$1 = popperOffsets[mainAxis] - overflow[altSide]; + var additive = tether ? -popperRect[len] / 2 : 0; + var minLen = variation === start ? referenceRect[len] : popperRect[len]; + var maxLen = variation === start ? -popperRect[len] : -referenceRect[len]; // We need to include the arrow in the calculation so the arrow doesn't go + // outside the reference bounds + + var arrowElement = state.elements.arrow; + var arrowRect = tether && arrowElement ? getLayoutRect(arrowElement) : { + width: 0, + height: 0, + }; + var arrowPaddingObject = state.modifiersData['arrow#persistent'] ? state.modifiersData['arrow#persistent'].padding : getFreshSideObject(); + var arrowPaddingMin = arrowPaddingObject[mainSide]; + var arrowPaddingMax = arrowPaddingObject[altSide]; // If the reference length is smaller than the arrow length, we don't want + // to include its full size in the calculation. If the reference is small + // and near the edge of a boundary, the popper can overflow even if the + // reference is not overflowing as well (e.g. virtual elements with no + // width or height) + + var arrowLen = within(0, referenceRect[len], arrowRect[len]); + var minOffset = isBasePlacement ? referenceRect[len] / 2 - additive - arrowLen - arrowPaddingMin - tetherOffsetValue : minLen - arrowLen - arrowPaddingMin - tetherOffsetValue; + var maxOffset = isBasePlacement ? -referenceRect[len] / 2 + additive + arrowLen + arrowPaddingMax + tetherOffsetValue : maxLen + arrowLen + arrowPaddingMax + tetherOffsetValue; + var arrowOffsetParent = state.elements.arrow && getOffsetParent(state.elements.arrow); + var clientOffset = arrowOffsetParent ? mainAxis === 'y' ? arrowOffsetParent.clientTop || 0 : arrowOffsetParent.clientLeft || 0 : 0; + var offsetModifierValue = state.modifiersData.offset ? state.modifiersData.offset[state.placement][mainAxis] : 0; + var tetherMin = popperOffsets[mainAxis] + minOffset - offsetModifierValue - clientOffset; + var tetherMax = popperOffsets[mainAxis] + maxOffset - offsetModifierValue; + + if ( checkMainAxis ) { + var preventedOffset = within(tether ? min(min$1, tetherMin) : min$1, offset, tether ? max(max$1, tetherMax) : max$1); + popperOffsets[mainAxis] = preventedOffset; + data[mainAxis] = preventedOffset - offset; + } + if ( checkAltAxis ) { + var _mainSide = mainAxis === 'x' ? top : left; - const preventOverflow$1 = { - name: 'preventOverflow', - enabled: true, - phase: 'main', - fn: preventOverflow, - requiresIfExists: ['offset'] - }; + var _altSide = mainAxis === 'x' ? bottom : right; - function getHTMLElementScroll(element) { - return { - scrollLeft: element.scrollLeft, - scrollTop: element.scrollTop - }; - } + var _offset = popperOffsets[altAxis]; - function getNodeScroll(node) { - if (node === getWindow(node) || !isHTMLElement(node)) { - return getWindowScroll(node); - } else { - return getHTMLElementScroll(node); - } - } + var _min = _offset + overflow[_mainSide]; - function isElementScaled(element) { - var rect = element.getBoundingClientRect(); - var scaleX = rect.width / element.offsetWidth || 1; - var scaleY = rect.height / element.offsetHeight || 1; - return scaleX !== 1 || scaleY !== 1; - } // Returns the composite rect of an element relative to its offsetParent. - // Composite means it takes into account transforms as well as layout. + var _max = _offset - overflow[_altSide]; + var _preventedOffset = within(tether ? min(_min, tetherMin) : _min, _offset, tether ? max(_max, tetherMax) : _max); - function getCompositeRect(elementOrVirtualElement, offsetParent, isFixed) { - if (isFixed === void 0) { - isFixed = false; - } + popperOffsets[altAxis] = _preventedOffset; + data[altAxis] = _preventedOffset - _offset; + } + } - var isOffsetParentAnElement = isHTMLElement(offsetParent); - isHTMLElement(offsetParent) && isElementScaled(offsetParent); - var documentElement = getDocumentElement(offsetParent); - var rect = getBoundingClientRect(elementOrVirtualElement); - var scroll = { - scrollLeft: 0, - scrollTop: 0 - }; - var offsets = { - x: 0, - y: 0 + state.modifiersData[name] = data; + } // eslint-disable-next-line import/no-unused-modules + + const preventOverflow$1 = { + name: 'preventOverflow', + enabled: true, + phase: 'main', + fn: preventOverflow, + requiresIfExists: ['offset'], }; - if (isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed) { - if (getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078 - isScrollParent(documentElement)) { - scroll = getNodeScroll(offsetParent); - } - - if (isHTMLElement(offsetParent)) { - offsets = getBoundingClientRect(offsetParent); - offsets.x += offsetParent.clientLeft; - offsets.y += offsetParent.clientTop; - } else if (documentElement) { - offsets.x = getWindowScrollBarX(documentElement); - } + function getHTMLElementScroll (element) { + return { + scrollLeft: element.scrollLeft, + scrollTop: element.scrollTop, + }; } - return { - x: rect.left + scroll.scrollLeft - offsets.x, - y: rect.top + scroll.scrollTop - offsets.y, - width: rect.width, - height: rect.height - }; - } - - function order(modifiers) { - var map = new Map(); - var visited = new Set(); - var result = []; - modifiers.forEach(function (modifier) { - map.set(modifier.name, modifier); - }); // On visiting object, check for its dependencies and visit them recursively - - function sort(modifier) { - visited.add(modifier.name); - var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []); - requires.forEach(function (dep) { - if (!visited.has(dep)) { - var depModifier = map.get(dep); - - if (depModifier) { - sort(depModifier); - } - } - }); - result.push(modifier); + function getNodeScroll (node) { + if ( node === getWindow(node) || !isHTMLElement(node) ) { + return getWindowScroll(node); + } else { + return getHTMLElementScroll(node); + } } - modifiers.forEach(function (modifier) { - if (!visited.has(modifier.name)) { - // check for visited object - sort(modifier); - } - }); - return result; - } + function isElementScaled (element) { + var rect = element.getBoundingClientRect(); + var scaleX = rect.width / element.offsetWidth || 1; + var scaleY = rect.height / element.offsetHeight || 1; + return scaleX !== 1 || scaleY !== 1; + } // Returns the composite rect of an element relative to its offsetParent. + // Composite means it takes into account transforms as well as layout. - function orderModifiers(modifiers) { - // order based on dependencies - var orderedModifiers = order(modifiers); // order based on phase + function getCompositeRect (elementOrVirtualElement, offsetParent, isFixed) { + if ( isFixed === void 0 ) { + isFixed = false; + } - return modifierPhases.reduce(function (acc, phase) { - return acc.concat(orderedModifiers.filter(function (modifier) { - return modifier.phase === phase; - })); - }, []); - } - - function debounce(fn) { - var pending; - return function () { - if (!pending) { - pending = new Promise(function (resolve) { - Promise.resolve().then(function () { - pending = undefined; - resolve(fn()); - }); - }); - } + var isOffsetParentAnElement = isHTMLElement(offsetParent); + isHTMLElement(offsetParent) && isElementScaled(offsetParent); + var documentElement = getDocumentElement(offsetParent); + var rect = getBoundingClientRect(elementOrVirtualElement); + var scroll = { + scrollLeft: 0, + scrollTop: 0, + }; + var offsets = { + x: 0, + y: 0, + }; - return pending; - }; - } - - function mergeByName(modifiers) { - var merged = modifiers.reduce(function (merged, current) { - var existing = merged[current.name]; - merged[current.name] = existing ? Object.assign({}, existing, current, { - options: Object.assign({}, existing.options, current.options), - data: Object.assign({}, existing.data, current.data) - }) : current; - return merged; - }, {}); // IE11 does not support Object.values - - return Object.keys(merged).map(function (key) { - return merged[key]; - }); - } + if ( isOffsetParentAnElement || !isOffsetParentAnElement && !isFixed ) { + if ( getNodeName(offsetParent) !== 'body' || // https://github.com/popperjs/popper-core/issues/1078 + isScrollParent(documentElement) ) { + scroll = getNodeScroll(offsetParent); + } - var DEFAULT_OPTIONS = { - placement: 'bottom', - modifiers: [], - strategy: 'absolute' - }; + if ( isHTMLElement(offsetParent) ) { + offsets = getBoundingClientRect(offsetParent); + offsets.x += offsetParent.clientLeft; + offsets.y += offsetParent.clientTop; + } else if ( documentElement ) { + offsets.x = getWindowScrollBarX(documentElement); + } + } - function areValidElements() { - for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { - args[_key] = arguments[_key]; + return { + x: rect.left + scroll.scrollLeft - offsets.x, + y: rect.top + scroll.scrollTop - offsets.y, + width: rect.width, + height: rect.height, + }; } - return !args.some(function (element) { - return !(element && typeof element.getBoundingClientRect === 'function'); - }); - } + function order (modifiers) { + var map = new Map(); + var visited = new Set(); + var result = []; + modifiers.forEach(function (modifier) { + map.set(modifier.name, modifier); + }); // On visiting object, check for its dependencies and visit them recursively + + function sort (modifier) { + visited.add(modifier.name); + var requires = [].concat(modifier.requires || [], modifier.requiresIfExists || []); + requires.forEach(function (dep) { + if ( ! visited.has(dep) ) { + var depModifier = map.get(dep); + + if ( depModifier ) { + sort(depModifier); + } + } + }); + result.push(modifier); + } - function popperGenerator(generatorOptions) { - if (generatorOptions === void 0) { - generatorOptions = {}; + modifiers.forEach(function (modifier) { + if ( ! visited.has(modifier.name) ) { + // check for visited object + sort(modifier); + } + }); + return result; } - var _generatorOptions = generatorOptions, - _generatorOptions$def = _generatorOptions.defaultModifiers, - defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def, - _generatorOptions$def2 = _generatorOptions.defaultOptions, - defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2; - return function createPopper(reference, popper, options) { - if (options === void 0) { - options = defaultOptions; - } - - var state = { - placement: 'bottom', - orderedModifiers: [], - options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions), - modifiersData: {}, - elements: { - reference: reference, - popper: popper - }, - attributes: {}, - styles: {} - }; - var effectCleanupFns = []; - var isDestroyed = false; - var instance = { - state: state, - setOptions: function setOptions(setOptionsAction) { - var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction; - cleanupModifierEffects(); - state.options = Object.assign({}, defaultOptions, state.options, options); - state.scrollParents = { - reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [], - popper: listScrollParents(popper) - }; // Orders the modifiers based on their dependencies and `phase` - // properties - - var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers - - state.orderedModifiers = orderedModifiers.filter(function (m) { - return m.enabled; - }); // Validate the provided modifiers so that the consumer will get warned - - runModifierEffects(); - return instance.update(); - }, - // Sync update – it will always be executed, even if not necessary. This - // is useful for low frequency updates where sync behavior simplifies the - // logic. - // For high frequency updates (e.g. `resize` and `scroll` events), always - // prefer the async Popper#update method - forceUpdate: function forceUpdate() { - if (isDestroyed) { - return; - } - - var _state$elements = state.elements, - reference = _state$elements.reference, - popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements - // anymore + function orderModifiers (modifiers) { + // order based on dependencies + var orderedModifiers = order(modifiers); // order based on phase + + return modifierPhases.reduce(function (acc, phase) { + return acc.concat(orderedModifiers.filter(function (modifier) { + return modifier.phase === phase; + })); + }, []); + } + + function debounce (fn) { + var pending; + return function () { + if ( ! pending ) { + pending = new Promise(function (resolve) { + Promise.resolve().then(function () { + pending = undefined; + resolve(fn()); + }); + }); + } - if (!areValidElements(reference, popper)) { + return pending; + }; + } - return; - } // Store the reference and popper rects to be read by modifiers + function mergeByName (modifiers) { + var merged = modifiers.reduce(function (merged, current) { + var existing = merged[current.name]; + merged[current.name] = existing ? Object.assign({}, existing, current, { + options: Object.assign({}, existing.options, current.options), + data: Object.assign({}, existing.data, current.data), + }) : current; + return merged; + }, {}); // IE11 does not support Object.values + return Object.keys(merged).map(function (key) { + return merged[key]; + }); + } - state.rects = { - reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'), - popper: getLayoutRect(popper) - }; // Modifiers have the ability to reset the current update cycle. The - // most common use case for this is the `flip` modifier changing the - // placement, which then needs to re-run all the modifiers, because the - // logic was previously ran for the previous placement and is therefore - // stale/incorrect + var DEFAULT_OPTIONS = { + placement: 'bottom', + modifiers: [], + strategy: 'absolute', + }; - state.reset = false; - state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier - // is filled with the initial data specified by the modifier. This means - // it doesn't persist and is fresh on each update. - // To ensure persistent data, use `${name}#persistent` + function areValidElements () { + for ( var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++ ) { + args[_key] = arguments[_key]; + } - state.orderedModifiers.forEach(function (modifier) { - return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); - }); + return !args.some(function (element) { + return !(element && typeof element.getBoundingClientRect === 'function'); + }); + } - for (var index = 0; index < state.orderedModifiers.length; index++) { + function popperGenerator (generatorOptions) { + if ( generatorOptions === void 0 ) { + generatorOptions = {}; + } - if (state.reset === true) { - state.reset = false; - index = -1; - continue; + var _generatorOptions = generatorOptions, + _generatorOptions$def = _generatorOptions.defaultModifiers, + defaultModifiers = _generatorOptions$def === void 0 ? [] : _generatorOptions$def, + _generatorOptions$def2 = _generatorOptions.defaultOptions, + defaultOptions = _generatorOptions$def2 === void 0 ? DEFAULT_OPTIONS : _generatorOptions$def2; + return function createPopper (reference, popper, options) { + if ( options === void 0 ) { + options = defaultOptions; } - var _state$orderedModifie = state.orderedModifiers[index], - fn = _state$orderedModifie.fn, - _state$orderedModifie2 = _state$orderedModifie.options, - _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2, - name = _state$orderedModifie.name; - - if (typeof fn === 'function') { - state = fn({ + var state = { + placement: 'bottom', + orderedModifiers: [], + options: Object.assign({}, DEFAULT_OPTIONS, defaultOptions), + modifiersData: {}, + elements: { + reference: reference, + popper: popper, + }, + attributes: {}, + styles: {}, + }; + var effectCleanupFns = []; + var isDestroyed = false; + var instance = { state: state, - options: _options, - name: name, - instance: instance - }) || state; + setOptions: function setOptions (setOptionsAction) { + var options = typeof setOptionsAction === 'function' ? setOptionsAction(state.options) : setOptionsAction; + cleanupModifierEffects(); + state.options = Object.assign({}, defaultOptions, state.options, options); + state.scrollParents = { + reference: isElement(reference) ? listScrollParents(reference) : reference.contextElement ? listScrollParents(reference.contextElement) : [], + popper: listScrollParents(popper), + }; // Orders the modifiers based on their dependencies and `phase` + // properties + + var orderedModifiers = orderModifiers(mergeByName([].concat(defaultModifiers, state.options.modifiers))); // Strip out disabled modifiers + + state.orderedModifiers = orderedModifiers.filter(function (m) { + return m.enabled; + }); // Validate the provided modifiers so that the consumer will get warned + + runModifierEffects(); + return instance.update(); + }, + // Sync update – it will always be executed, even if not necessary. This + // is useful for low frequency updates where sync behavior simplifies the + // logic. + // For high frequency updates (e.g. `resize` and `scroll` events), always + // prefer the async Popper#update method + forceUpdate: function forceUpdate () { + if ( isDestroyed ) { + return; + } + + var _state$elements = state.elements, + reference = _state$elements.reference, + popper = _state$elements.popper; // Don't proceed if `reference` or `popper` are not valid elements + // anymore + + if ( ! areValidElements(reference, popper) ) { + + return; + } // Store the reference and popper rects to be read by modifiers + + state.rects = { + reference: getCompositeRect(reference, getOffsetParent(popper), state.options.strategy === 'fixed'), + popper: getLayoutRect(popper), + }; // Modifiers have the ability to reset the current update cycle. The + // most common use case for this is the `flip` modifier changing the + // placement, which then needs to re-run all the modifiers, because the + // logic was previously ran for the previous placement and is therefore + // stale/incorrect + + state.reset = false; + state.placement = state.options.placement; // On each update cycle, the `modifiersData` property for each modifier + // is filled with the initial data specified by the modifier. This means + // it doesn't persist and is fresh on each update. + // To ensure persistent data, use `${name}#persistent` + + state.orderedModifiers.forEach(function (modifier) { + return state.modifiersData[modifier.name] = Object.assign({}, modifier.data); + }); + + for ( var index = 0; index < state.orderedModifiers.length; index++ ) { + + if ( state.reset === true ) { + state.reset = false; + index = -1; + continue; + } + + var _state$orderedModifie = state.orderedModifiers[index], + fn = _state$orderedModifie.fn, + _state$orderedModifie2 = _state$orderedModifie.options, + _options = _state$orderedModifie2 === void 0 ? {} : _state$orderedModifie2, + name = _state$orderedModifie.name; + + if ( typeof fn === 'function' ) { + state = fn({ + state: state, + options: _options, + name: name, + instance: instance, + }) || state; + } + } + }, + // Async and optimistically optimized update – it will not be executed if + // not necessary (debounced to run at most once-per-tick) + update: debounce(function () { + return new Promise(function (resolve) { + instance.forceUpdate(); + resolve(state); + }); + }), + destroy: function destroy () { + cleanupModifierEffects(); + isDestroyed = true; + }, + }; + + if ( ! areValidElements(reference, popper) ) { + + return instance; } - } - }, - // Async and optimistically optimized update – it will not be executed if - // not necessary (debounced to run at most once-per-tick) - update: debounce(function () { - return new Promise(function (resolve) { - instance.forceUpdate(); - resolve(state); - }); - }), - destroy: function destroy() { - cleanupModifierEffects(); - isDestroyed = true; - } - }; - - if (!areValidElements(reference, popper)) { - - return instance; - } - - instance.setOptions(options).then(function (state) { - if (!isDestroyed && options.onFirstUpdate) { - options.onFirstUpdate(state); - } - }); // Modifiers have the ability to execute arbitrary code before the first - // update cycle runs. They will be executed in the same order as the update - // cycle. This is useful when a modifier adds some persistent data that - // other modifiers need to use, but the modifier is run after the dependent - // one. - - function runModifierEffects() { - state.orderedModifiers.forEach(function (_ref3) { - var name = _ref3.name, - _ref3$options = _ref3.options, - options = _ref3$options === void 0 ? {} : _ref3$options, - effect = _ref3.effect; - - if (typeof effect === 'function') { - var cleanupFn = effect({ - state: state, - name: name, - instance: instance, - options: options - }); - var noopFn = function noopFn() {}; + instance.setOptions(options).then(function (state) { + if ( !isDestroyed && options.onFirstUpdate ) { + options.onFirstUpdate(state); + } + }); // Modifiers have the ability to execute arbitrary code before the first + // update cycle runs. They will be executed in the same order as the update + // cycle. This is useful when a modifier adds some persistent data that + // other modifiers need to use, but the modifier is run after the dependent + // one. + + function runModifierEffects () { + state.orderedModifiers.forEach(function (_ref3) { + var name = _ref3.name, + _ref3$options = _ref3.options, + options = _ref3$options === void 0 ? {} : _ref3$options, + effect = _ref3.effect; + + if ( typeof effect === 'function' ) { + var cleanupFn = effect({ + state: state, + name: name, + instance: instance, + options: options, + }); + + var noopFn = function noopFn () { + }; + + effectCleanupFns.push(cleanupFn || noopFn); + } + }); + } - effectCleanupFns.push(cleanupFn || noopFn); - } - }); - } + function cleanupModifierEffects () { + effectCleanupFns.forEach(function (fn) { + return fn(); + }); + effectCleanupFns = []; + } - function cleanupModifierEffects() { - effectCleanupFns.forEach(function (fn) { - return fn(); - }); - effectCleanupFns = []; - } + return instance; + }; + } + var createPopper$2 = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules + + var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1]; + var createPopper$1 = /*#__PURE__*/popperGenerator({ + defaultModifiers: defaultModifiers$1, + }); // eslint-disable-next-line import/no-unused-modules + + var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1]; + var createPopper = /*#__PURE__*/popperGenerator({ + defaultModifiers: defaultModifiers, + }); // eslint-disable-next-line import/no-unused-modules + + const Popper = /*#__PURE__*/Object.freeze({ + __proto__: null, + popperGenerator, + detectOverflow, + createPopperBase: createPopper$2, + createPopper, + createPopperLite: createPopper$1, + top, + bottom, + right, + left, + auto, + basePlacements, + start, + end, + clippingParents, + viewport, + popper, + reference, + variationPlacements, + placements, + beforeRead, + read, + afterRead, + beforeMain, + main, + afterMain, + beforeWrite, + write, + afterWrite, + modifierPhases, + applyStyles: applyStyles$1, + arrow: arrow$1, + computeStyles: computeStyles$1, + eventListeners, + flip: flip$1, + hide: hide$1, + offset: offset$1, + popperOffsets: popperOffsets$1, + preventOverflow: preventOverflow$1, + }); - return instance; - }; - } - var createPopper$2 = /*#__PURE__*/popperGenerator(); // eslint-disable-next-line import/no-unused-modules - - var defaultModifiers$1 = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1]; - var createPopper$1 = /*#__PURE__*/popperGenerator({ - defaultModifiers: defaultModifiers$1 - }); // eslint-disable-next-line import/no-unused-modules - - var defaultModifiers = [eventListeners, popperOffsets$1, computeStyles$1, applyStyles$1, offset$1, flip$1, preventOverflow$1, arrow$1, hide$1]; - var createPopper = /*#__PURE__*/popperGenerator({ - defaultModifiers: defaultModifiers - }); // eslint-disable-next-line import/no-unused-modules - - const Popper = /*#__PURE__*/Object.freeze({ - __proto__: null, - popperGenerator, - detectOverflow, - createPopperBase: createPopper$2, - createPopper, - createPopperLite: createPopper$1, - top, - bottom, - right, - left, - auto, - basePlacements, - start, - end, - clippingParents, - viewport, - popper, - reference, - variationPlacements, - placements, - beforeRead, - read, - afterRead, - beforeMain, - main, - afterMain, - beforeWrite, - write, - afterWrite, - modifierPhases, - applyStyles: applyStyles$1, - arrow: arrow$1, - computeStyles: computeStyles$1, - eventListeners, - flip: flip$1, - hide: hide$1, - offset: offset$1, - popperOffsets: popperOffsets$1, - preventOverflow: preventOverflow$1 - }); - - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): dropdown.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$9 = 'dropdown'; - const DATA_KEY$8 = 'bs.dropdown'; - const EVENT_KEY$8 = `.${DATA_KEY$8}`; - const DATA_API_KEY$4 = '.data-api'; - const ESCAPE_KEY$2 = 'Escape'; - const SPACE_KEY = 'Space'; - const TAB_KEY$1 = 'Tab'; - const ARROW_UP_KEY = 'ArrowUp'; - const ARROW_DOWN_KEY = 'ArrowDown'; - const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button - - const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY$2}`); - const EVENT_HIDE$4 = `hide${EVENT_KEY$8}`; - const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$8}`; - const EVENT_SHOW$4 = `show${EVENT_KEY$8}`; - const EVENT_SHOWN$4 = `shown${EVENT_KEY$8}`; - const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$8}${DATA_API_KEY$4}`; - const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$8}${DATA_API_KEY$4}`; - const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$8}${DATA_API_KEY$4}`; - const CLASS_NAME_SHOW$6 = 'show'; - const CLASS_NAME_DROPUP = 'dropup'; - const CLASS_NAME_DROPEND = 'dropend'; - const CLASS_NAME_DROPSTART = 'dropstart'; - const CLASS_NAME_NAVBAR = 'navbar'; - const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle="dropdown"]'; - const SELECTOR_MENU = '.dropdown-menu'; - const SELECTOR_NAVBAR_NAV = '.navbar-nav'; - const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'; - const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'; - const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'; - const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'; - const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'; - const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'; - const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'; - const Default$8 = { - offset: [0, 2], - boundary: 'clippingParents', - reference: 'toggle', - display: 'dynamic', - popperConfig: null, - autoClose: true - }; - const DefaultType$8 = { - offset: '(array|string|function)', - boundary: '(string|element)', - reference: '(string|element|object)', - display: 'string', - popperConfig: '(null|object|function)', - autoClose: '(boolean|string)' - }; - /** + const NAME$9 = 'dropdown'; + const DATA_KEY$8 = 'bs.dropdown'; + const EVENT_KEY$8 = `.${DATA_KEY$8}`; + const DATA_API_KEY$4 = '.data-api'; + const ESCAPE_KEY$2 = 'Escape'; + const SPACE_KEY = 'Space'; + const TAB_KEY$1 = 'Tab'; + const ARROW_UP_KEY = 'ArrowUp'; + const ARROW_DOWN_KEY = 'ArrowDown'; + const RIGHT_MOUSE_BUTTON = 2; // MouseEvent.button value for the secondary button, usually the right button + + const REGEXP_KEYDOWN = new RegExp(`${ARROW_UP_KEY}|${ARROW_DOWN_KEY}|${ESCAPE_KEY$2}`); + const EVENT_HIDE$4 = `hide${EVENT_KEY$8}`; + const EVENT_HIDDEN$4 = `hidden${EVENT_KEY$8}`; + const EVENT_SHOW$4 = `show${EVENT_KEY$8}`; + const EVENT_SHOWN$4 = `shown${EVENT_KEY$8}`; + const EVENT_CLICK_DATA_API$3 = `click${EVENT_KEY$8}${DATA_API_KEY$4}`; + const EVENT_KEYDOWN_DATA_API = `keydown${EVENT_KEY$8}${DATA_API_KEY$4}`; + const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY$8}${DATA_API_KEY$4}`; + const CLASS_NAME_SHOW$6 = 'show'; + const CLASS_NAME_DROPUP = 'dropup'; + const CLASS_NAME_DROPEND = 'dropend'; + const CLASS_NAME_DROPSTART = 'dropstart'; + const CLASS_NAME_NAVBAR = 'navbar'; + const SELECTOR_DATA_TOGGLE$3 = '[data-bs-toggle="dropdown"]'; + const SELECTOR_MENU = '.dropdown-menu'; + const SELECTOR_NAVBAR_NAV = '.navbar-nav'; + const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'; + const PLACEMENT_TOP = isRTL() ? 'top-end' : 'top-start'; + const PLACEMENT_TOPEND = isRTL() ? 'top-start' : 'top-end'; + const PLACEMENT_BOTTOM = isRTL() ? 'bottom-end' : 'bottom-start'; + const PLACEMENT_BOTTOMEND = isRTL() ? 'bottom-start' : 'bottom-end'; + const PLACEMENT_RIGHT = isRTL() ? 'left-start' : 'right-start'; + const PLACEMENT_LEFT = isRTL() ? 'right-start' : 'left-start'; + const Default$8 = { + offset: [0, 2], + boundary: 'clippingParents', + reference: 'toggle', + display: 'dynamic', + popperConfig: null, + autoClose: true, + }; + const DefaultType$8 = { + offset: '(array|string|function)', + boundary: '(string|element)', + reference: '(string|element|object)', + display: 'string', + popperConfig: '(null|object|function)', + autoClose: '(boolean|string)', + }; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Dropdown extends BaseComponent { - constructor(element, config) { - super(element); - this._popper = null; - this._config = this._getConfig(config); - this._menu = this._getMenuElement(); - this._inNavbar = this._detectNavbar(); - } // Getters - - - static get Default() { - return Default$8; - } - - static get DefaultType() { - return DefaultType$8; - } - - static get NAME() { - return NAME$9; - } // Public - - - toggle() { - return this._isShown() ? this.hide() : this.show(); - } - - show() { - if (isDisabled(this._element) || this._isShown(this._menu)) { - return; - } + class Dropdown extends BaseComponent { + constructor (element, config) { + super(element); + this._popper = null; + this._config = this._getConfig(config); + this._menu = this._getMenuElement(); + this._inNavbar = this._detectNavbar(); + } // Getters + + static get Default () { + return Default$8; + } - const relatedTarget = { - relatedTarget: this._element - }; - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, relatedTarget); + static get DefaultType () { + return DefaultType$8; + } - if (showEvent.defaultPrevented) { - return; - } + static get NAME () { + return NAME$9; + } // Public - const parent = Dropdown.getParentFromElement(this._element); // Totally disable Popper for Dropdowns in Navbar + toggle () { + return this._isShown() ? this.hide() : this.show(); + } - if (this._inNavbar) { - Manipulator.setDataAttribute(this._menu, 'popper', 'none'); - } else { - this._createPopper(parent); - } // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + show () { + if ( isDisabled(this._element) || this._isShown(this._menu) ) { + return; + } + const relatedTarget = { + relatedTarget: this._element, + }; + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$4, relatedTarget); - if ('ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV)) { - [].concat(...document.body.children).forEach(elem => EventHandler.on(elem, 'mouseover', noop)); - } + if ( showEvent.defaultPrevented ) { + return; + } - this._element.focus(); + const parent = Dropdown.getParentFromElement(this._element); // Totally disable Popper for Dropdowns in Navbar - this._element.setAttribute('aria-expanded', true); + if ( this._inNavbar ) { + Manipulator.setDataAttribute(this._menu, 'popper', 'none'); + } else { + this._createPopper(parent); + } // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - this._menu.classList.add(CLASS_NAME_SHOW$6); + if ( 'ontouchstart' in document.documentElement && !parent.closest(SELECTOR_NAVBAR_NAV) ) { + [].concat(...document.body.children).forEach(elem => EventHandler.on(elem, 'mouseover', noop)); + } - this._element.classList.add(CLASS_NAME_SHOW$6); + this._element.focus(); - EventHandler.trigger(this._element, EVENT_SHOWN$4, relatedTarget); - } + this._element.setAttribute('aria-expanded', true); - hide() { - if (isDisabled(this._element) || !this._isShown(this._menu)) { - return; - } + this._menu.classList.add(CLASS_NAME_SHOW$6); - const relatedTarget = { - relatedTarget: this._element - }; + this._element.classList.add(CLASS_NAME_SHOW$6); - this._completeHide(relatedTarget); - } + EventHandler.trigger(this._element, EVENT_SHOWN$4, relatedTarget); + } - dispose() { - if (this._popper) { - this._popper.destroy(); - } + hide () { + if ( isDisabled(this._element) || !this._isShown(this._menu) ) { + return; + } - super.dispose(); - } + const relatedTarget = { + relatedTarget: this._element, + }; - update() { - this._inNavbar = this._detectNavbar(); + this._completeHide(relatedTarget); + } - if (this._popper) { - this._popper.update(); - } - } // Private + dispose () { + if ( this._popper ) { + this._popper.destroy(); + } + super.dispose(); + } - _completeHide(relatedTarget) { - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4, relatedTarget); + update () { + this._inNavbar = this._detectNavbar(); - if (hideEvent.defaultPrevented) { - return; - } // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support + if ( this._popper ) { + this._popper.update(); + } + } // Private + _completeHide (relatedTarget) { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$4, relatedTarget); - if ('ontouchstart' in document.documentElement) { - [].concat(...document.body.children).forEach(elem => EventHandler.off(elem, 'mouseover', noop)); - } + if ( hideEvent.defaultPrevented ) { + return; + } // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support - if (this._popper) { - this._popper.destroy(); - } + if ( 'ontouchstart' in document.documentElement ) { + [].concat(...document.body.children).forEach(elem => EventHandler.off(elem, 'mouseover', noop)); + } - this._menu.classList.remove(CLASS_NAME_SHOW$6); + if ( this._popper ) { + this._popper.destroy(); + } - this._element.classList.remove(CLASS_NAME_SHOW$6); + this._menu.classList.remove(CLASS_NAME_SHOW$6); - this._element.setAttribute('aria-expanded', 'false'); + this._element.classList.remove(CLASS_NAME_SHOW$6); - Manipulator.removeDataAttribute(this._menu, 'popper'); - EventHandler.trigger(this._element, EVENT_HIDDEN$4, relatedTarget); - } + this._element.setAttribute('aria-expanded', 'false'); - _getConfig(config) { - config = { ...this.constructor.Default, - ...Manipulator.getDataAttributes(this._element), - ...config - }; - typeCheckConfig(NAME$9, config, this.constructor.DefaultType); + Manipulator.removeDataAttribute(this._menu, 'popper'); + EventHandler.trigger(this._element, EVENT_HIDDEN$4, relatedTarget); + } - if (typeof config.reference === 'object' && !isElement$1(config.reference) && typeof config.reference.getBoundingClientRect !== 'function') { - // Popper virtual elements require a getBoundingClientRect method - throw new TypeError(`${NAME$9.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); - } + _getConfig (config) { + config = { ...this.constructor.Default, + ...Manipulator.getDataAttributes(this._element), + ...config, + }; + typeCheckConfig(NAME$9, config, this.constructor.DefaultType); - return config; - } + if ( typeof config.reference === 'object' && !isElement$1(config.reference) && typeof config.reference.getBoundingClientRect !== 'function' ) { + // Popper virtual elements require a getBoundingClientRect method + throw new TypeError(`${NAME$9.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`); + } - _createPopper(parent) { - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); - } + return config; + } - let referenceElement = this._element; + _createPopper (parent) { + if ( typeof Popper === 'undefined' ) { + throw new TypeError('Bootstrap\'s dropdowns require Popper (https://popper.js.org)'); + } - if (this._config.reference === 'parent') { - referenceElement = parent; - } else if (isElement$1(this._config.reference)) { - referenceElement = getElement(this._config.reference); - } else if (typeof this._config.reference === 'object') { - referenceElement = this._config.reference; - } + let referenceElement = this._element; - const popperConfig = this._getPopperConfig(); + if ( this._config.reference === 'parent' ) { + referenceElement = parent; + } else if ( isElement$1(this._config.reference) ) { + referenceElement = getElement(this._config.reference); + } else if ( typeof this._config.reference === 'object' ) { + referenceElement = this._config.reference; + } - const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false); - this._popper = createPopper(referenceElement, this._menu, popperConfig); + const popperConfig = this._getPopperConfig(); - if (isDisplayStatic) { - Manipulator.setDataAttribute(this._menu, 'popper', 'static'); - } - } + const isDisplayStatic = popperConfig.modifiers.find(modifier => modifier.name === 'applyStyles' && modifier.enabled === false); + this._popper = createPopper(referenceElement, this._menu, popperConfig); - _isShown(element = this._element) { - return element.classList.contains(CLASS_NAME_SHOW$6); - } + if ( isDisplayStatic ) { + Manipulator.setDataAttribute(this._menu, 'popper', 'static'); + } + } - _getMenuElement() { - return SelectorEngine.next(this._element, SELECTOR_MENU)[0]; - } + _isShown (element = this._element) { + return element.classList.contains(CLASS_NAME_SHOW$6); + } - _getPlacement() { - const parentDropdown = this._element.parentNode; + _getMenuElement () { + return SelectorEngine.next(this._element, SELECTOR_MENU)[0]; + } - if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) { - return PLACEMENT_RIGHT; - } + _getPlacement () { + const parentDropdown = this._element.parentNode; - if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) { - return PLACEMENT_LEFT; - } // We need to trim the value because custom properties can also include spaces + if ( parentDropdown.classList.contains(CLASS_NAME_DROPEND) ) { + return PLACEMENT_RIGHT; + } + if ( parentDropdown.classList.contains(CLASS_NAME_DROPSTART) ) { + return PLACEMENT_LEFT; + } // We need to trim the value because custom properties can also include spaces - const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; + const isEnd = getComputedStyle(this._menu).getPropertyValue('--bs-position').trim() === 'end'; - if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) { - return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; - } + if ( parentDropdown.classList.contains(CLASS_NAME_DROPUP) ) { + return isEnd ? PLACEMENT_TOPEND : PLACEMENT_TOP; + } - return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; - } + return isEnd ? PLACEMENT_BOTTOMEND : PLACEMENT_BOTTOM; + } - _detectNavbar() { - return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null; - } + _detectNavbar () { + return this._element.closest(`.${CLASS_NAME_NAVBAR}`) !== null; + } - _getOffset() { - const { - offset - } = this._config; + _getOffset () { + const { + offset, + } = this._config; - if (typeof offset === 'string') { - return offset.split(',').map(val => Number.parseInt(val, 10)); - } + if ( typeof offset === 'string' ) { + return offset.split(',').map(val => Number.parseInt(val, 10)); + } - if (typeof offset === 'function') { - return popperData => offset(popperData, this._element); - } + if ( typeof offset === 'function' ) { + return popperData => offset(popperData, this._element); + } - return offset; - } + return offset; + } - _getPopperConfig() { - const defaultBsPopperConfig = { - placement: this._getPlacement(), - modifiers: [{ - name: 'preventOverflow', - options: { - boundary: this._config.boundary - } - }, { - name: 'offset', - options: { - offset: this._getOffset() - } - }] - }; // Disable Popper if we have a static display - - if (this._config.display === 'static') { - defaultBsPopperConfig.modifiers = [{ - name: 'applyStyles', - enabled: false - }]; - } - - return { ...defaultBsPopperConfig, - ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) - }; - } + _getPopperConfig () { + const defaultBsPopperConfig = { + placement: this._getPlacement(), + modifiers: [{ + name: 'preventOverflow', + options: { + boundary: this._config.boundary, + }, + }, { + name: 'offset', + options: { + offset: this._getOffset(), + }, + }], + }; // Disable Popper if we have a static display + + if ( this._config.display === 'static' ) { + defaultBsPopperConfig.modifiers = [{ + name: 'applyStyles', + enabled: false, + }]; + } - _selectMenuItem({ - key, - target - }) { - const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible); + return { ...defaultBsPopperConfig, + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig), + }; + } - if (!items.length) { - return; - } // if target isn't included in items (e.g. when expanding the dropdown) - // allow cycling to get the last item in case key equals ARROW_UP_KEY + _selectMenuItem ({ + key, + target, + }) { + const items = SelectorEngine.find(SELECTOR_VISIBLE_ITEMS, this._menu).filter(isVisible); + if ( ! items.length ) { + return; + } // if target isn't included in items (e.g. when expanding the dropdown) + // allow cycling to get the last item in case key equals ARROW_UP_KEY - getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus(); - } // Static + getNextActiveElement(items, target, key === ARROW_DOWN_KEY, !items.includes(target)).focus(); + } // Static + static jQueryInterface (config) { + return this.each(function () { + const data = Dropdown.getOrCreateInstance(this, config); - static jQueryInterface(config) { - return this.each(function () { - const data = Dropdown.getOrCreateInstance(this, config); + if ( typeof config !== 'string' ) { + return; + } - if (typeof config !== 'string') { - return; - } + if ( typeof data[config] === 'undefined' ) { + throw new TypeError(`No method named "${config}"`); + } - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); + data[config](); + }); } - data[config](); - }); - } - - static clearMenus(event) { - if (event && (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1)) { - return; - } + static clearMenus (event) { + if ( event && (event.button === RIGHT_MOUSE_BUTTON || event.type === 'keyup' && event.key !== TAB_KEY$1) ) { + return; + } - const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE$3); + const toggles = SelectorEngine.find(SELECTOR_DATA_TOGGLE$3); - for (let i = 0, len = toggles.length; i < len; i++) { - const context = Dropdown.getInstance(toggles[i]); + for ( let i = 0, len = toggles.length; i < len; i++ ) { + const context = Dropdown.getInstance(toggles[i]); - if (!context || context._config.autoClose === false) { - continue; - } + if ( !context || context._config.autoClose === false ) { + continue; + } - if (!context._isShown()) { - continue; - } + if ( ! context._isShown() ) { + continue; + } - const relatedTarget = { - relatedTarget: context._element - }; + const relatedTarget = { + relatedTarget: context._element, + }; - if (event) { - const composedPath = event.composedPath(); - const isMenuTarget = composedPath.includes(context._menu); + if ( event ) { + const composedPath = event.composedPath(); + const isMenuTarget = composedPath.includes(context._menu); - if (composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget) { - continue; - } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if ( composedPath.includes(context._element) || context._config.autoClose === 'inside' && !isMenuTarget || context._config.autoClose === 'outside' && isMenuTarget ) { + continue; + } // Tab navigation through the dropdown menu or events from contained inputs shouldn't close the menu + if ( context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName)) ) { + continue; + } - if (context._menu.contains(event.target) && (event.type === 'keyup' && event.key === TAB_KEY$1 || /input|select|option|textarea|form/i.test(event.target.tagName))) { - continue; - } + if ( event.type === 'click' ) { + relatedTarget.clickEvent = event; + } + } - if (event.type === 'click') { - relatedTarget.clickEvent = event; - } + context._completeHide(relatedTarget); + } } - context._completeHide(relatedTarget); - } - } + static getParentFromElement (element) { + return getElementFromSelector(element) || element.parentNode; + } - static getParentFromElement(element) { - return getElementFromSelector(element) || element.parentNode; - } + static dataApiKeydownHandler (event) { + // If not input/textarea: + // - And not a key in REGEXP_KEYDOWN => not a dropdown command + // If input/textarea: + // - If space key => not a dropdown command + // - If key is other than escape + // - If key is not up or down => not a dropdown command + // - If trigger inside the menu => not a dropdown command + if ( /input|textarea/i.test(event.target.tagName) ? event.key === SPACE_KEY || event.key !== ESCAPE_KEY$2 && (event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY || event.target.closest(SELECTOR_MENU)) : !REGEXP_KEYDOWN.test(event.key) ) { + return; + } - static dataApiKeydownHandler(event) { - // If not input/textarea: - // - And not a key in REGEXP_KEYDOWN => not a dropdown command - // If input/textarea: - // - If space key => not a dropdown command - // - If key is other than escape - // - If key is not up or down => not a dropdown command - // - If trigger inside the menu => not a dropdown command - if (/input|textarea/i.test(event.target.tagName) ? event.key === SPACE_KEY || event.key !== ESCAPE_KEY$2 && (event.key !== ARROW_DOWN_KEY && event.key !== ARROW_UP_KEY || event.target.closest(SELECTOR_MENU)) : !REGEXP_KEYDOWN.test(event.key)) { - return; - } + const isActive = this.classList.contains(CLASS_NAME_SHOW$6); - const isActive = this.classList.contains(CLASS_NAME_SHOW$6); + if ( !isActive && event.key === ESCAPE_KEY$2 ) { + return; + } - if (!isActive && event.key === ESCAPE_KEY$2) { - return; - } + event.preventDefault(); + event.stopPropagation(); - event.preventDefault(); - event.stopPropagation(); + if ( isDisabled(this) ) { + return; + } - if (isDisabled(this)) { - return; - } + const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0]; + const instance = Dropdown.getOrCreateInstance(getToggleButton); - const getToggleButton = this.matches(SELECTOR_DATA_TOGGLE$3) ? this : SelectorEngine.prev(this, SELECTOR_DATA_TOGGLE$3)[0]; - const instance = Dropdown.getOrCreateInstance(getToggleButton); + if ( event.key === ESCAPE_KEY$2 ) { + instance.hide(); + return; + } - if (event.key === ESCAPE_KEY$2) { - instance.hide(); - return; - } + if ( event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY ) { + if ( ! isActive ) { + instance.show(); + } - if (event.key === ARROW_UP_KEY || event.key === ARROW_DOWN_KEY) { - if (!isActive) { - instance.show(); - } + instance._selectMenuItem(event); - instance._selectMenuItem(event); + return; + } - return; - } + if ( !isActive || event.key === SPACE_KEY ) { + Dropdown.clearMenus(); + } + } - if (!isActive || event.key === SPACE_KEY) { - Dropdown.clearMenus(); - } } - - } - /** + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ - - EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); - EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); - EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); - EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus); - EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) { - event.preventDefault(); - Dropdown.getOrCreateInstance(this).toggle(); - }); - /** + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_DATA_TOGGLE$3, Dropdown.dataApiKeydownHandler); + EventHandler.on(document, EVENT_KEYDOWN_DATA_API, SELECTOR_MENU, Dropdown.dataApiKeydownHandler); + EventHandler.on(document, EVENT_CLICK_DATA_API$3, Dropdown.clearMenus); + EventHandler.on(document, EVENT_KEYUP_DATA_API, Dropdown.clearMenus); + EventHandler.on(document, EVENT_CLICK_DATA_API$3, SELECTOR_DATA_TOGGLE$3, function (event) { + event.preventDefault(); + Dropdown.getOrCreateInstance(this).toggle(); + }); + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Dropdown to jQuery only if jQuery is present */ - defineJQueryPlugin(Dropdown); + defineJQueryPlugin(Dropdown); - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/scrollBar.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; - const SELECTOR_STICKY_CONTENT = '.sticky-top'; - - class ScrollBarHelper { - constructor() { - this._element = document.body; - } + const SELECTOR_FIXED_CONTENT = '.fixed-top, .fixed-bottom, .is-fixed, .sticky-top'; + const SELECTOR_STICKY_CONTENT = '.sticky-top'; - getWidth() { - // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes - const documentWidth = document.documentElement.clientWidth; - return Math.abs(window.innerWidth - documentWidth); - } + class ScrollBarHelper { + constructor () { + this._element = document.body; + } - hide() { - const width = this.getWidth(); + getWidth () { + // https://developer.mozilla.org/en-US/docs/Web/API/Window/innerWidth#usage_notes + const documentWidth = document.documentElement.clientWidth; + return Math.abs(window.innerWidth - documentWidth); + } - this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width + hide () { + const width = this.getWidth(); + this._disableOverFlow(); // give padding to element to balance the hidden scrollbar width - this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + this._setElementAttributes(this._element, 'paddingRight', calculatedValue => calculatedValue + width); // trick: We adjust positive paddingRight and negative marginRight to sticky-top elements to keep showing fullwidth + this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width); - this._setElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight', calculatedValue => calculatedValue + width); + this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width); + } - this._setElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight', calculatedValue => calculatedValue - width); - } + _disableOverFlow () { + this._saveInitialAttribute(this._element, 'overflow'); - _disableOverFlow() { - this._saveInitialAttribute(this._element, 'overflow'); + this._element.style.overflow = 'hidden'; + } - this._element.style.overflow = 'hidden'; - } + _setElementAttributes (selector, styleProp, callback) { + const scrollbarWidth = this.getWidth(); - _setElementAttributes(selector, styleProp, callback) { - const scrollbarWidth = this.getWidth(); + const manipulationCallBack = element => { + if ( element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth ) { + return; + } - const manipulationCallBack = element => { - if (element !== this._element && window.innerWidth > element.clientWidth + scrollbarWidth) { - return; - } + this._saveInitialAttribute(element, styleProp); - this._saveInitialAttribute(element, styleProp); + const calculatedValue = window.getComputedStyle(element)[styleProp]; + element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px`; + }; - const calculatedValue = window.getComputedStyle(element)[styleProp]; - element.style[styleProp] = `${callback(Number.parseFloat(calculatedValue))}px`; - }; + this._applyManipulationCallback(selector, manipulationCallBack); + } - this._applyManipulationCallback(selector, manipulationCallBack); - } + reset () { + this._resetElementAttributes(this._element, 'overflow'); - reset() { - this._resetElementAttributes(this._element, 'overflow'); + this._resetElementAttributes(this._element, 'paddingRight'); - this._resetElementAttributes(this._element, 'paddingRight'); + this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight'); - this._resetElementAttributes(SELECTOR_FIXED_CONTENT, 'paddingRight'); + this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight'); + } - this._resetElementAttributes(SELECTOR_STICKY_CONTENT, 'marginRight'); - } + _saveInitialAttribute (element, styleProp) { + const actualValue = element.style[styleProp]; - _saveInitialAttribute(element, styleProp) { - const actualValue = element.style[styleProp]; + if ( actualValue ) { + Manipulator.setDataAttribute(element, styleProp, actualValue); + } + } - if (actualValue) { - Manipulator.setDataAttribute(element, styleProp, actualValue); - } - } + _resetElementAttributes (selector, styleProp) { + const manipulationCallBack = element => { + const value = Manipulator.getDataAttribute(element, styleProp); - _resetElementAttributes(selector, styleProp) { - const manipulationCallBack = element => { - const value = Manipulator.getDataAttribute(element, styleProp); + if ( typeof value === 'undefined' ) { + element.style.removeProperty(styleProp); + } else { + Manipulator.removeDataAttribute(element, styleProp); + element.style[styleProp] = value; + } + }; - if (typeof value === 'undefined') { - element.style.removeProperty(styleProp); - } else { - Manipulator.removeDataAttribute(element, styleProp); - element.style[styleProp] = value; + this._applyManipulationCallback(selector, manipulationCallBack); } - }; - this._applyManipulationCallback(selector, manipulationCallBack); - } + _applyManipulationCallback (selector, callBack) { + if ( isElement$1(selector) ) { + callBack(selector); + } else { + SelectorEngine.find(selector, this._element).forEach(callBack); + } + } - _applyManipulationCallback(selector, callBack) { - if (isElement$1(selector)) { - callBack(selector); - } else { - SelectorEngine.find(selector, this._element).forEach(callBack); - } - } + isOverflowing () { + return this.getWidth() > 0; + } - isOverflowing() { - return this.getWidth() > 0; } - } - - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/backdrop.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const Default$7 = { - className: 'modal-backdrop', - isVisible: true, - // if false, we use the backdrop helper without adding any element to the dom - isAnimated: false, - rootElement: 'body', - // give the choice to place backdrop under different elements - clickCallback: null - }; - const DefaultType$7 = { - className: 'string', - isVisible: 'boolean', - isAnimated: 'boolean', - rootElement: '(element|string)', - clickCallback: '(function|null)' - }; - const NAME$8 = 'backdrop'; - const CLASS_NAME_FADE$4 = 'fade'; - const CLASS_NAME_SHOW$5 = 'show'; - const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$8}`; - - class Backdrop { - constructor(config) { - this._config = this._getConfig(config); - this._isAppended = false; - this._element = null; - } + const Default$7 = { + className: 'modal-backdrop', + isVisible: true, + // if false, we use the backdrop helper without adding any element to the dom + isAnimated: false, + rootElement: 'body', + // give the choice to place backdrop under different elements + clickCallback: null, + }; + const DefaultType$7 = { + className: 'string', + isVisible: 'boolean', + isAnimated: 'boolean', + rootElement: '(element|string)', + clickCallback: '(function|null)', + }; + const NAME$8 = 'backdrop'; + const CLASS_NAME_FADE$4 = 'fade'; + const CLASS_NAME_SHOW$5 = 'show'; + const EVENT_MOUSEDOWN = `mousedown.bs.${NAME$8}`; + + class Backdrop { + constructor (config) { + this._config = this._getConfig(config); + this._isAppended = false; + this._element = null; + } + + show (callback) { + if ( ! this._config.isVisible ) { + execute(callback); + return; + } - show(callback) { - if (!this._config.isVisible) { - execute(callback); - return; - } + this._append(); - this._append(); + if ( this._config.isAnimated ) { + reflow(this._getElement()); + } - if (this._config.isAnimated) { - reflow(this._getElement()); - } + this._getElement().classList.add(CLASS_NAME_SHOW$5); - this._getElement().classList.add(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { + execute(callback); + }); + } - this._emulateAnimation(() => { - execute(callback); - }); - } + hide (callback) { + if ( ! this._config.isVisible ) { + execute(callback); + return; + } - hide(callback) { - if (!this._config.isVisible) { - execute(callback); - return; - } + this._getElement().classList.remove(CLASS_NAME_SHOW$5); - this._getElement().classList.remove(CLASS_NAME_SHOW$5); + this._emulateAnimation(() => { + this.dispose(); + execute(callback); + }); + } // Private - this._emulateAnimation(() => { - this.dispose(); - execute(callback); - }); - } // Private + _getElement () { + if ( ! this._element ) { + const backdrop = document.createElement('div'); + backdrop.className = this._config.className; + if ( this._config.isAnimated ) { + backdrop.classList.add(CLASS_NAME_FADE$4); + } - _getElement() { - if (!this._element) { - const backdrop = document.createElement('div'); - backdrop.className = this._config.className; + this._element = backdrop; + } - if (this._config.isAnimated) { - backdrop.classList.add(CLASS_NAME_FADE$4); + return this._element; } - this._element = backdrop; - } - - return this._element; - } + _getConfig (config) { + config = { ...Default$7, + ...(typeof config === 'object' ? config : {}), + }; // use getElement() with the default "body" to get a fresh Element on each instantiation - _getConfig(config) { - config = { ...Default$7, - ...(typeof config === 'object' ? config : {}) - }; // use getElement() with the default "body" to get a fresh Element on each instantiation + config.rootElement = getElement(config.rootElement); + typeCheckConfig(NAME$8, config, DefaultType$7); + return config; + } - config.rootElement = getElement(config.rootElement); - typeCheckConfig(NAME$8, config, DefaultType$7); - return config; - } + _append () { + if ( this._isAppended ) { + return; + } - _append() { - if (this._isAppended) { - return; - } + this._config.rootElement.append(this._getElement()); - this._config.rootElement.append(this._getElement()); + EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => { + execute(this._config.clickCallback); + }); + this._isAppended = true; + } - EventHandler.on(this._getElement(), EVENT_MOUSEDOWN, () => { - execute(this._config.clickCallback); - }); - this._isAppended = true; - } + dispose () { + if ( ! this._isAppended ) { + return; + } - dispose() { - if (!this._isAppended) { - return; - } + EventHandler.off(this._element, EVENT_MOUSEDOWN); - EventHandler.off(this._element, EVENT_MOUSEDOWN); + this._element.remove(); - this._element.remove(); + this._isAppended = false; + } - this._isAppended = false; - } + _emulateAnimation (callback) { + executeAfterTransition(callback, this._getElement(), this._config.isAnimated); + } - _emulateAnimation(callback) { - executeAfterTransition(callback, this._getElement(), this._config.isAnimated); } - } - - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/focustrap.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const Default$6 = { - trapElement: null, - // The element to trap focus inside of - autofocus: true - }; - const DefaultType$6 = { - trapElement: 'element', - autofocus: 'boolean' - }; - const NAME$7 = 'focustrap'; - const DATA_KEY$7 = 'bs.focustrap'; - const EVENT_KEY$7 = `.${DATA_KEY$7}`; - const EVENT_FOCUSIN$1 = `focusin${EVENT_KEY$7}`; - const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$7}`; - const TAB_KEY = 'Tab'; - const TAB_NAV_FORWARD = 'forward'; - const TAB_NAV_BACKWARD = 'backward'; - - class FocusTrap { - constructor(config) { - this._config = this._getConfig(config); - this._isActive = false; - this._lastTabNavDirection = null; - } + const Default$6 = { + trapElement: null, + // The element to trap focus inside of + autofocus: true, + }; + const DefaultType$6 = { + trapElement: 'element', + autofocus: 'boolean', + }; + const NAME$7 = 'focustrap'; + const DATA_KEY$7 = 'bs.focustrap'; + const EVENT_KEY$7 = `.${DATA_KEY$7}`; + const EVENT_FOCUSIN$1 = `focusin${EVENT_KEY$7}`; + const EVENT_KEYDOWN_TAB = `keydown.tab${EVENT_KEY$7}`; + const TAB_KEY = 'Tab'; + const TAB_NAV_FORWARD = 'forward'; + const TAB_NAV_BACKWARD = 'backward'; + + class FocusTrap { + constructor (config) { + this._config = this._getConfig(config); + this._isActive = false; + this._lastTabNavDirection = null; + } - activate() { - const { - trapElement, - autofocus - } = this._config; + activate () { + const { + trapElement, + autofocus, + } = this._config; - if (this._isActive) { - return; - } + if ( this._isActive ) { + return; + } - if (autofocus) { - trapElement.focus(); - } + if ( autofocus ) { + trapElement.focus(); + } - EventHandler.off(document, EVENT_KEY$7); // guard against infinite focus loop + EventHandler.off(document, EVENT_KEY$7); // guard against infinite focus loop - EventHandler.on(document, EVENT_FOCUSIN$1, event => this._handleFocusin(event)); - EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); - this._isActive = true; - } + EventHandler.on(document, EVENT_FOCUSIN$1, event => this._handleFocusin(event)); + EventHandler.on(document, EVENT_KEYDOWN_TAB, event => this._handleKeydown(event)); + this._isActive = true; + } - deactivate() { - if (!this._isActive) { - return; - } - - this._isActive = false; - EventHandler.off(document, EVENT_KEY$7); - } // Private - - - _handleFocusin(event) { - const { - target - } = event; - const { - trapElement - } = this._config; - - if (target === document || target === trapElement || trapElement.contains(target)) { - return; - } - - const elements = SelectorEngine.focusableChildren(trapElement); - - if (elements.length === 0) { - trapElement.focus(); - } else if (this._lastTabNavDirection === TAB_NAV_BACKWARD) { - elements[elements.length - 1].focus(); - } else { - elements[0].focus(); - } - } + deactivate () { + if ( ! this._isActive ) { + return; + } - _handleKeydown(event) { - if (event.key !== TAB_KEY) { - return; - } + this._isActive = false; + EventHandler.off(document, EVENT_KEY$7); + } // Private - this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; - } + _handleFocusin (event) { + const { + target, + } = event; + const { + trapElement, + } = this._config; - _getConfig(config) { - config = { ...Default$6, - ...(typeof config === 'object' ? config : {}) - }; - typeCheckConfig(NAME$7, config, DefaultType$6); - return config; - } + if ( target === document || target === trapElement || trapElement.contains(target) ) { + return; + } + + const elements = SelectorEngine.focusableChildren(trapElement); + + if ( elements.length === 0 ) { + trapElement.focus(); + } else if ( this._lastTabNavDirection === TAB_NAV_BACKWARD ) { + elements[elements.length - 1].focus(); + } else { + elements[0].focus(); + } + } + + _handleKeydown (event) { + if ( event.key !== TAB_KEY ) { + return; + } + + this._lastTabNavDirection = event.shiftKey ? TAB_NAV_BACKWARD : TAB_NAV_FORWARD; + } + + _getConfig (config) { + config = { ...Default$6, + ...(typeof config === 'object' ? config : {}), + }; + typeCheckConfig(NAME$7, config, DefaultType$6); + return config; + } - } + } - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): modal.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$6 = 'modal'; - const DATA_KEY$6 = 'bs.modal'; - const EVENT_KEY$6 = `.${DATA_KEY$6}`; - const DATA_API_KEY$3 = '.data-api'; - const ESCAPE_KEY$1 = 'Escape'; - const Default$5 = { - backdrop: true, - keyboard: true, - focus: true - }; - const DefaultType$5 = { - backdrop: '(boolean|string)', - keyboard: 'boolean', - focus: 'boolean' - }; - const EVENT_HIDE$3 = `hide${EVENT_KEY$6}`; - const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$6}`; - const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$6}`; - const EVENT_SHOW$3 = `show${EVENT_KEY$6}`; - const EVENT_SHOWN$3 = `shown${EVENT_KEY$6}`; - const EVENT_RESIZE = `resize${EVENT_KEY$6}`; - const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$6}`; - const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$6}`; - const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY$6}`; - const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$6}`; - const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`; - const CLASS_NAME_OPEN = 'modal-open'; - const CLASS_NAME_FADE$3 = 'fade'; - const CLASS_NAME_SHOW$4 = 'show'; - const CLASS_NAME_STATIC = 'modal-static'; - const OPEN_SELECTOR$1 = '.modal.show'; - const SELECTOR_DIALOG = '.modal-dialog'; - const SELECTOR_MODAL_BODY = '.modal-body'; - const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle="modal"]'; - /** + const NAME$6 = 'modal'; + const DATA_KEY$6 = 'bs.modal'; + const EVENT_KEY$6 = `.${DATA_KEY$6}`; + const DATA_API_KEY$3 = '.data-api'; + const ESCAPE_KEY$1 = 'Escape'; + const Default$5 = { + backdrop: true, + keyboard: true, + focus: true, + }; + const DefaultType$5 = { + backdrop: '(boolean|string)', + keyboard: 'boolean', + focus: 'boolean', + }; + const EVENT_HIDE$3 = `hide${EVENT_KEY$6}`; + const EVENT_HIDE_PREVENTED = `hidePrevented${EVENT_KEY$6}`; + const EVENT_HIDDEN$3 = `hidden${EVENT_KEY$6}`; + const EVENT_SHOW$3 = `show${EVENT_KEY$6}`; + const EVENT_SHOWN$3 = `shown${EVENT_KEY$6}`; + const EVENT_RESIZE = `resize${EVENT_KEY$6}`; + const EVENT_CLICK_DISMISS = `click.dismiss${EVENT_KEY$6}`; + const EVENT_KEYDOWN_DISMISS$1 = `keydown.dismiss${EVENT_KEY$6}`; + const EVENT_MOUSEUP_DISMISS = `mouseup.dismiss${EVENT_KEY$6}`; + const EVENT_MOUSEDOWN_DISMISS = `mousedown.dismiss${EVENT_KEY$6}`; + const EVENT_CLICK_DATA_API$2 = `click${EVENT_KEY$6}${DATA_API_KEY$3}`; + const CLASS_NAME_OPEN = 'modal-open'; + const CLASS_NAME_FADE$3 = 'fade'; + const CLASS_NAME_SHOW$4 = 'show'; + const CLASS_NAME_STATIC = 'modal-static'; + const OPEN_SELECTOR$1 = '.modal.show'; + const SELECTOR_DIALOG = '.modal-dialog'; + const SELECTOR_MODAL_BODY = '.modal-body'; + const SELECTOR_DATA_TOGGLE$2 = '[data-bs-toggle="modal"]'; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Modal extends BaseComponent { - constructor(element, config) { - super(element); - this._config = this._getConfig(config); - this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); - this._backdrop = this._initializeBackDrop(); - this._focustrap = this._initializeFocusTrap(); - this._isShown = false; - this._ignoreBackdropClick = false; - this._isTransitioning = false; - this._scrollBar = new ScrollBarHelper(); - } // Getters - - - static get Default() { - return Default$5; - } + class Modal extends BaseComponent { + constructor (element, config) { + super(element); + this._config = this._getConfig(config); + this._dialog = SelectorEngine.findOne(SELECTOR_DIALOG, this._element); + this._backdrop = this._initializeBackDrop(); + this._focustrap = this._initializeFocusTrap(); + this._isShown = false; + this._ignoreBackdropClick = false; + this._isTransitioning = false; + this._scrollBar = new ScrollBarHelper(); + } // Getters + + static get Default () { + return Default$5; + } - static get NAME() { - return NAME$6; - } // Public + static get NAME () { + return NAME$6; + } // Public + toggle (relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + } - toggle(relatedTarget) { - return this._isShown ? this.hide() : this.show(relatedTarget); - } + show (relatedTarget) { + if ( this._isShown || this._isTransitioning ) { + return; + } - show(relatedTarget) { - if (this._isShown || this._isTransitioning) { - return; - } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { + relatedTarget, + }); - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$3, { - relatedTarget - }); + if ( showEvent.defaultPrevented ) { + return; + } - if (showEvent.defaultPrevented) { - return; - } + this._isShown = true; - this._isShown = true; + if ( this._isAnimated() ) { + this._isTransitioning = true; + } - if (this._isAnimated()) { - this._isTransitioning = true; - } + this._scrollBar.hide(); - this._scrollBar.hide(); + document.body.classList.add(CLASS_NAME_OPEN); - document.body.classList.add(CLASS_NAME_OPEN); + this._adjustDialog(); - this._adjustDialog(); + this._setEscapeEvent(); - this._setEscapeEvent(); + this._setResizeEvent(); - this._setResizeEvent(); + EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => { + EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => { + if ( event.target === this._element ) { + this._ignoreBackdropClick = true; + } + }); + }); - EventHandler.on(this._dialog, EVENT_MOUSEDOWN_DISMISS, () => { - EventHandler.one(this._element, EVENT_MOUSEUP_DISMISS, event => { - if (event.target === this._element) { - this._ignoreBackdropClick = true; - } - }); - }); + this._showBackdrop(() => this._showElement(relatedTarget)); + } - this._showBackdrop(() => this._showElement(relatedTarget)); - } + hide () { + if ( !this._isShown || this._isTransitioning ) { + return; + } - hide() { - if (!this._isShown || this._isTransitioning) { - return; - } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$3); + if ( hideEvent.defaultPrevented ) { + return; + } - if (hideEvent.defaultPrevented) { - return; - } + this._isShown = false; - this._isShown = false; + const isAnimated = this._isAnimated(); - const isAnimated = this._isAnimated(); + if ( isAnimated ) { + this._isTransitioning = true; + } - if (isAnimated) { - this._isTransitioning = true; - } + this._setEscapeEvent(); - this._setEscapeEvent(); + this._setResizeEvent(); - this._setResizeEvent(); + this._focustrap.deactivate(); - this._focustrap.deactivate(); + this._element.classList.remove(CLASS_NAME_SHOW$4); - this._element.classList.remove(CLASS_NAME_SHOW$4); + EventHandler.off(this._element, EVENT_CLICK_DISMISS); + EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS); - EventHandler.off(this._element, EVENT_CLICK_DISMISS); - EventHandler.off(this._dialog, EVENT_MOUSEDOWN_DISMISS); + this._queueCallback(() => this._hideModal(), this._element, isAnimated); + } - this._queueCallback(() => this._hideModal(), this._element, isAnimated); - } + dispose () { + [window, this._dialog].forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY$6)); - dispose() { - [window, this._dialog].forEach(htmlElement => EventHandler.off(htmlElement, EVENT_KEY$6)); + this._backdrop.dispose(); - this._backdrop.dispose(); + this._focustrap.deactivate(); - this._focustrap.deactivate(); + super.dispose(); + } - super.dispose(); - } + handleUpdate () { + this._adjustDialog(); + } // Private - handleUpdate() { - this._adjustDialog(); - } // Private + _initializeBackDrop () { + return new Backdrop({ + isVisible: Boolean(this._config.backdrop), + // 'static' option will be translated to true, and booleans will keep their value + isAnimated: this._isAnimated(), + }); + } + _initializeFocusTrap () { + return new FocusTrap({ + trapElement: this._element, + }); + } - _initializeBackDrop() { - return new Backdrop({ - isVisible: Boolean(this._config.backdrop), - // 'static' option will be translated to true, and booleans will keep their value - isAnimated: this._isAnimated() - }); - } + _getConfig (config) { + config = { ...Default$5, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}), + }; + typeCheckConfig(NAME$6, config, DefaultType$5); + return config; + } - _initializeFocusTrap() { - return new FocusTrap({ - trapElement: this._element - }); - } + _showElement (relatedTarget) { + const isAnimated = this._isAnimated(); - _getConfig(config) { - config = { ...Default$5, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' ? config : {}) - }; - typeCheckConfig(NAME$6, config, DefaultType$5); - return config; - } + const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); - _showElement(relatedTarget) { - const isAnimated = this._isAnimated(); + if ( !this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE ) { + // Don't move modal's DOM position + document.body.append(this._element); + } - const modalBody = SelectorEngine.findOne(SELECTOR_MODAL_BODY, this._dialog); + this._element.style.display = 'block'; - if (!this._element.parentNode || this._element.parentNode.nodeType !== Node.ELEMENT_NODE) { - // Don't move modal's DOM position - document.body.append(this._element); - } + this._element.removeAttribute('aria-hidden'); - this._element.style.display = 'block'; + this._element.setAttribute('aria-modal', true); - this._element.removeAttribute('aria-hidden'); + this._element.setAttribute('role', 'dialog'); - this._element.setAttribute('aria-modal', true); + this._element.scrollTop = 0; - this._element.setAttribute('role', 'dialog'); + if ( modalBody ) { + modalBody.scrollTop = 0; + } - this._element.scrollTop = 0; + if ( isAnimated ) { + reflow(this._element); + } - if (modalBody) { - modalBody.scrollTop = 0; - } + this._element.classList.add(CLASS_NAME_SHOW$4); - if (isAnimated) { - reflow(this._element); - } + const transitionComplete = () => { + if ( this._config.focus ) { + this._focustrap.activate(); + } - this._element.classList.add(CLASS_NAME_SHOW$4); + this._isTransitioning = false; + EventHandler.trigger(this._element, EVENT_SHOWN$3, { + relatedTarget, + }); + }; - const transitionComplete = () => { - if (this._config.focus) { - this._focustrap.activate(); + this._queueCallback(transitionComplete, this._dialog, isAnimated); } - this._isTransitioning = false; - EventHandler.trigger(this._element, EVENT_SHOWN$3, { - relatedTarget - }); - }; - - this._queueCallback(transitionComplete, this._dialog, isAnimated); - } - - _setEscapeEvent() { - if (this._isShown) { - EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { - if (this._config.keyboard && event.key === ESCAPE_KEY$1) { - event.preventDefault(); - this.hide(); - } else if (!this._config.keyboard && event.key === ESCAPE_KEY$1) { - this._triggerBackdropTransition(); - } - }); - } else { - EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS$1); - } - } - - _setResizeEvent() { - if (this._isShown) { - EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog()); - } else { - EventHandler.off(window, EVENT_RESIZE); - } - } + _setEscapeEvent () { + if ( this._isShown ) { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS$1, event => { + if ( this._config.keyboard && event.key === ESCAPE_KEY$1 ) { + event.preventDefault(); + this.hide(); + } else if ( !this._config.keyboard && event.key === ESCAPE_KEY$1 ) { + this._triggerBackdropTransition(); + } + }); + } else { + EventHandler.off(this._element, EVENT_KEYDOWN_DISMISS$1); + } + } - _hideModal() { - this._element.style.display = 'none'; + _setResizeEvent () { + if ( this._isShown ) { + EventHandler.on(window, EVENT_RESIZE, () => this._adjustDialog()); + } else { + EventHandler.off(window, EVENT_RESIZE); + } + } - this._element.setAttribute('aria-hidden', true); + _hideModal () { + this._element.style.display = 'none'; - this._element.removeAttribute('aria-modal'); + this._element.setAttribute('aria-hidden', true); - this._element.removeAttribute('role'); + this._element.removeAttribute('aria-modal'); - this._isTransitioning = false; + this._element.removeAttribute('role'); - this._backdrop.hide(() => { - document.body.classList.remove(CLASS_NAME_OPEN); + this._isTransitioning = false; - this._resetAdjustments(); + this._backdrop.hide(() => { + document.body.classList.remove(CLASS_NAME_OPEN); - this._scrollBar.reset(); + this._resetAdjustments(); - EventHandler.trigger(this._element, EVENT_HIDDEN$3); - }); - } + this._scrollBar.reset(); - _showBackdrop(callback) { - EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { - if (this._ignoreBackdropClick) { - this._ignoreBackdropClick = false; - return; + EventHandler.trigger(this._element, EVENT_HIDDEN$3); + }); } - if (event.target !== event.currentTarget) { - return; - } + _showBackdrop (callback) { + EventHandler.on(this._element, EVENT_CLICK_DISMISS, event => { + if ( this._ignoreBackdropClick ) { + this._ignoreBackdropClick = false; + return; + } + + if ( event.target !== event.currentTarget ) { + return; + } + + if ( this._config.backdrop === true ) { + this.hide(); + } else if ( this._config.backdrop === 'static' ) { + this._triggerBackdropTransition(); + } + }); - if (this._config.backdrop === true) { - this.hide(); - } else if (this._config.backdrop === 'static') { - this._triggerBackdropTransition(); + this._backdrop.show(callback); } - }); - - this._backdrop.show(callback); - } - - _isAnimated() { - return this._element.classList.contains(CLASS_NAME_FADE$3); - } - _triggerBackdropTransition() { - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); + _isAnimated () { + return this._element.classList.contains(CLASS_NAME_FADE$3); + } - if (hideEvent.defaultPrevented) { - return; - } + _triggerBackdropTransition () { + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE_PREVENTED); - const { - classList, - scrollHeight, - style - } = this._element; - const isModalOverflowing = scrollHeight > document.documentElement.clientHeight; // return if the following background transition hasn't yet completed + if ( hideEvent.defaultPrevented ) { + return; + } - if (!isModalOverflowing && style.overflowY === 'hidden' || classList.contains(CLASS_NAME_STATIC)) { - return; - } + const { + classList, + scrollHeight, + style, + } = this._element; + const isModalOverflowing = scrollHeight > document.documentElement.clientHeight; // return if the following background transition hasn't yet completed - if (!isModalOverflowing) { - style.overflowY = 'hidden'; - } + if ( !isModalOverflowing && style.overflowY === 'hidden' || classList.contains(CLASS_NAME_STATIC) ) { + return; + } - classList.add(CLASS_NAME_STATIC); + if ( ! isModalOverflowing ) { + style.overflowY = 'hidden'; + } - this._queueCallback(() => { - classList.remove(CLASS_NAME_STATIC); + classList.add(CLASS_NAME_STATIC); - if (!isModalOverflowing) { - this._queueCallback(() => { - style.overflowY = ''; - }, this._dialog); - } - }, this._dialog); + this._queueCallback(() => { + classList.remove(CLASS_NAME_STATIC); - this._element.focus(); - } // ---------------------------------------------------------------------- - // the following methods are used to handle overflowing modals - // ---------------------------------------------------------------------- + if ( ! isModalOverflowing ) { + this._queueCallback(() => { + style.overflowY = ''; + }, this._dialog); + } + }, this._dialog); + this._element.focus(); + } // ---------------------------------------------------------------------- + // the following methods are used to handle overflowing modals + // ---------------------------------------------------------------------- - _adjustDialog() { - const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; + _adjustDialog () { + const isModalOverflowing = this._element.scrollHeight > document.documentElement.clientHeight; - const scrollbarWidth = this._scrollBar.getWidth(); + const scrollbarWidth = this._scrollBar.getWidth(); - const isBodyOverflowing = scrollbarWidth > 0; + const isBodyOverflowing = scrollbarWidth > 0; - if (!isBodyOverflowing && isModalOverflowing && !isRTL() || isBodyOverflowing && !isModalOverflowing && isRTL()) { - this._element.style.paddingLeft = `${scrollbarWidth}px`; - } + if ( !isBodyOverflowing && isModalOverflowing && !isRTL() || isBodyOverflowing && !isModalOverflowing && isRTL() ) { + this._element.style.paddingLeft = `${scrollbarWidth}px`; + } - if (isBodyOverflowing && !isModalOverflowing && !isRTL() || !isBodyOverflowing && isModalOverflowing && isRTL()) { - this._element.style.paddingRight = `${scrollbarWidth}px`; - } - } + if ( isBodyOverflowing && !isModalOverflowing && !isRTL() || !isBodyOverflowing && isModalOverflowing && isRTL() ) { + this._element.style.paddingRight = `${scrollbarWidth}px`; + } + } - _resetAdjustments() { - this._element.style.paddingLeft = ''; - this._element.style.paddingRight = ''; - } // Static + _resetAdjustments () { + this._element.style.paddingLeft = ''; + this._element.style.paddingRight = ''; + } // Static + static jQueryInterface (config, relatedTarget) { + return this.each(function () { + const data = Modal.getOrCreateInstance(this, config); - static jQueryInterface(config, relatedTarget) { - return this.each(function () { - const data = Modal.getOrCreateInstance(this, config); + if ( typeof config !== 'string' ) { + return; + } - if (typeof config !== 'string') { - return; - } + if ( typeof data[config] === 'undefined' ) { + throw new TypeError(`No method named "${config}"`); + } - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); + data[config](relatedTarget); + }); } - data[config](relatedTarget); - }); } - - } - /** + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ + EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { + const target = getElementFromSelector(this); - EventHandler.on(document, EVENT_CLICK_DATA_API$2, SELECTOR_DATA_TOGGLE$2, function (event) { - const target = getElementFromSelector(this); - - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault(); - } + if ( ['A', 'AREA'].includes(this.tagName) ) { + event.preventDefault(); + } - EventHandler.one(target, EVENT_SHOW$3, showEvent => { - if (showEvent.defaultPrevented) { - // only register focus restorer if modal will actually get shown - return; - } + EventHandler.one(target, EVENT_SHOW$3, showEvent => { + if ( showEvent.defaultPrevented ) { + // only register focus restorer if modal will actually get shown + return; + } - EventHandler.one(target, EVENT_HIDDEN$3, () => { - if (isVisible(this)) { - this.focus(); - } - }); - }); // avoid conflict when clicking moddal toggler while another one is open + EventHandler.one(target, EVENT_HIDDEN$3, () => { + if ( isVisible(this) ) { + this.focus(); + } + }); + }); // avoid conflict when clicking moddal toggler while another one is open - const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); + const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR$1); - if (allReadyOpen) { - Modal.getInstance(allReadyOpen).hide(); - } + if ( allReadyOpen ) { + Modal.getInstance(allReadyOpen).hide(); + } - const data = Modal.getOrCreateInstance(target); - data.toggle(this); - }); - enableDismissTrigger(Modal); - /** + const data = Modal.getOrCreateInstance(target); + data.toggle(this); + }); + enableDismissTrigger(Modal); + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Modal to jQuery only if jQuery is present */ - defineJQueryPlugin(Modal); + defineJQueryPlugin(Modal); - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): offcanvas.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$5 = 'offcanvas'; - const DATA_KEY$5 = 'bs.offcanvas'; - const EVENT_KEY$5 = `.${DATA_KEY$5}`; - const DATA_API_KEY$2 = '.data-api'; - const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$5}${DATA_API_KEY$2}`; - const ESCAPE_KEY = 'Escape'; - const Default$4 = { - backdrop: true, - keyboard: true, - scroll: false - }; - const DefaultType$4 = { - backdrop: 'boolean', - keyboard: 'boolean', - scroll: 'boolean' - }; - const CLASS_NAME_SHOW$3 = 'show'; - const CLASS_NAME_BACKDROP = 'offcanvas-backdrop'; - const OPEN_SELECTOR = '.offcanvas.show'; - const EVENT_SHOW$2 = `show${EVENT_KEY$5}`; - const EVENT_SHOWN$2 = `shown${EVENT_KEY$5}`; - const EVENT_HIDE$2 = `hide${EVENT_KEY$5}`; - const EVENT_HIDDEN$2 = `hidden${EVENT_KEY$5}`; - const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$5}${DATA_API_KEY$2}`; - const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$5}`; - const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle="offcanvas"]'; - /** + const NAME$5 = 'offcanvas'; + const DATA_KEY$5 = 'bs.offcanvas'; + const EVENT_KEY$5 = `.${DATA_KEY$5}`; + const DATA_API_KEY$2 = '.data-api'; + const EVENT_LOAD_DATA_API$1 = `load${EVENT_KEY$5}${DATA_API_KEY$2}`; + const ESCAPE_KEY = 'Escape'; + const Default$4 = { + backdrop: true, + keyboard: true, + scroll: false, + }; + const DefaultType$4 = { + backdrop: 'boolean', + keyboard: 'boolean', + scroll: 'boolean', + }; + const CLASS_NAME_SHOW$3 = 'show'; + const CLASS_NAME_BACKDROP = 'offcanvas-backdrop'; + const OPEN_SELECTOR = '.offcanvas.show'; + const EVENT_SHOW$2 = `show${EVENT_KEY$5}`; + const EVENT_SHOWN$2 = `shown${EVENT_KEY$5}`; + const EVENT_HIDE$2 = `hide${EVENT_KEY$5}`; + const EVENT_HIDDEN$2 = `hidden${EVENT_KEY$5}`; + const EVENT_CLICK_DATA_API$1 = `click${EVENT_KEY$5}${DATA_API_KEY$2}`; + const EVENT_KEYDOWN_DISMISS = `keydown.dismiss${EVENT_KEY$5}`; + const SELECTOR_DATA_TOGGLE$1 = '[data-bs-toggle="offcanvas"]'; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Offcanvas extends BaseComponent { - constructor(element, config) { - super(element); - this._config = this._getConfig(config); - this._isShown = false; - this._backdrop = this._initializeBackDrop(); - this._focustrap = this._initializeFocusTrap(); + class Offcanvas extends BaseComponent { + constructor (element, config) { + super(element); + this._config = this._getConfig(config); + this._isShown = false; + this._backdrop = this._initializeBackDrop(); + this._focustrap = this._initializeFocusTrap(); - this._addEventListeners(); - } // Getters + this._addEventListeners(); + } // Getters + static get NAME () { + return NAME$5; + } - static get NAME() { - return NAME$5; - } + static get Default () { + return Default$4; + } // Public - static get Default() { - return Default$4; - } // Public + toggle (relatedTarget) { + return this._isShown ? this.hide() : this.show(relatedTarget); + } + show (relatedTarget) { + if ( this._isShown ) { + return; + } - toggle(relatedTarget) { - return this._isShown ? this.hide() : this.show(relatedTarget); - } + const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$2, { + relatedTarget, + }); - show(relatedTarget) { - if (this._isShown) { - return; - } + if ( showEvent.defaultPrevented ) { + return; + } - const showEvent = EventHandler.trigger(this._element, EVENT_SHOW$2, { - relatedTarget - }); + this._isShown = true; + this._element.style.visibility = 'visible'; - if (showEvent.defaultPrevented) { - return; - } + this._backdrop.show(); - this._isShown = true; - this._element.style.visibility = 'visible'; + if ( ! this._config.scroll ) { + new ScrollBarHelper().hide(); + } - this._backdrop.show(); + this._element.removeAttribute('aria-hidden'); - if (!this._config.scroll) { - new ScrollBarHelper().hide(); - } + this._element.setAttribute('aria-modal', true); - this._element.removeAttribute('aria-hidden'); + this._element.setAttribute('role', 'dialog'); - this._element.setAttribute('aria-modal', true); + this._element.classList.add(CLASS_NAME_SHOW$3); - this._element.setAttribute('role', 'dialog'); + const completeCallBack = () => { + if ( ! this._config.scroll ) { + this._focustrap.activate(); + } - this._element.classList.add(CLASS_NAME_SHOW$3); + EventHandler.trigger(this._element, EVENT_SHOWN$2, { + relatedTarget, + }); + }; - const completeCallBack = () => { - if (!this._config.scroll) { - this._focustrap.activate(); + this._queueCallback(completeCallBack, this._element, true); } - EventHandler.trigger(this._element, EVENT_SHOWN$2, { - relatedTarget - }); - }; + hide () { + if ( ! this._isShown ) { + return; + } - this._queueCallback(completeCallBack, this._element, true); - } + const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$2); - hide() { - if (!this._isShown) { - return; - } + if ( hideEvent.defaultPrevented ) { + return; + } - const hideEvent = EventHandler.trigger(this._element, EVENT_HIDE$2); + this._focustrap.deactivate(); - if (hideEvent.defaultPrevented) { - return; - } + this._element.blur(); - this._focustrap.deactivate(); + this._isShown = false; - this._element.blur(); + this._element.classList.remove(CLASS_NAME_SHOW$3); - this._isShown = false; + this._backdrop.hide(); - this._element.classList.remove(CLASS_NAME_SHOW$3); + const completeCallback = () => { + this._element.setAttribute('aria-hidden', true); - this._backdrop.hide(); + this._element.removeAttribute('aria-modal'); - const completeCallback = () => { - this._element.setAttribute('aria-hidden', true); + this._element.removeAttribute('role'); - this._element.removeAttribute('aria-modal'); + this._element.style.visibility = 'hidden'; - this._element.removeAttribute('role'); + if ( ! this._config.scroll ) { + new ScrollBarHelper().reset(); + } - this._element.style.visibility = 'hidden'; + EventHandler.trigger(this._element, EVENT_HIDDEN$2); + }; - if (!this._config.scroll) { - new ScrollBarHelper().reset(); + this._queueCallback(completeCallback, this._element, true); } - EventHandler.trigger(this._element, EVENT_HIDDEN$2); - }; - - this._queueCallback(completeCallback, this._element, true); - } - - dispose() { - this._backdrop.dispose(); + dispose () { + this._backdrop.dispose(); - this._focustrap.deactivate(); + this._focustrap.deactivate(); - super.dispose(); - } // Private + super.dispose(); + } // Private + _getConfig (config) { + config = { ...Default$4, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' ? config : {}), + }; + typeCheckConfig(NAME$5, config, DefaultType$4); + return config; + } - _getConfig(config) { - config = { ...Default$4, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' ? config : {}) - }; - typeCheckConfig(NAME$5, config, DefaultType$4); - return config; - } - - _initializeBackDrop() { - return new Backdrop({ - className: CLASS_NAME_BACKDROP, - isVisible: this._config.backdrop, - isAnimated: true, - rootElement: this._element.parentNode, - clickCallback: () => this.hide() - }); - } - - _initializeFocusTrap() { - return new FocusTrap({ - trapElement: this._element - }); - } + _initializeBackDrop () { + return new Backdrop({ + className: CLASS_NAME_BACKDROP, + isVisible: this._config.backdrop, + isAnimated: true, + rootElement: this._element.parentNode, + clickCallback: () => this.hide(), + }); + } - _addEventListeners() { - EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { - if (this._config.keyboard && event.key === ESCAPE_KEY) { - this.hide(); + _initializeFocusTrap () { + return new FocusTrap({ + trapElement: this._element, + }); } - }); - } // Static + _addEventListeners () { + EventHandler.on(this._element, EVENT_KEYDOWN_DISMISS, event => { + if ( this._config.keyboard && event.key === ESCAPE_KEY ) { + this.hide(); + } + }); + } // Static + + static jQueryInterface (config) { + return this.each(function () { + const data = Offcanvas.getOrCreateInstance(this, config); - static jQueryInterface(config) { - return this.each(function () { - const data = Offcanvas.getOrCreateInstance(this, config); + if ( typeof config !== 'string' ) { + return; + } - if (typeof config !== 'string') { - return; - } + if ( data[config] === undefined || config.startsWith('_') || config === 'constructor' ) { + throw new TypeError(`No method named "${config}"`); + } - if (data[config] === undefined || config.startsWith('_') || config === 'constructor') { - throw new TypeError(`No method named "${config}"`); + data[config](this); + }); } - data[config](this); - }); } - - } - /** + /** * ------------------------------------------------------------------------ * Data Api implementation * ------------------------------------------------------------------------ */ + EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { + const target = getElementFromSelector(this); - EventHandler.on(document, EVENT_CLICK_DATA_API$1, SELECTOR_DATA_TOGGLE$1, function (event) { - const target = getElementFromSelector(this); - - if (['A', 'AREA'].includes(this.tagName)) { - event.preventDefault(); - } + if ( ['A', 'AREA'].includes(this.tagName) ) { + event.preventDefault(); + } - if (isDisabled(this)) { - return; - } + if ( isDisabled(this) ) { + return; + } - EventHandler.one(target, EVENT_HIDDEN$2, () => { - // focus on trigger when it is closed - if (isVisible(this)) { - this.focus(); - } - }); // avoid conflict when clicking a toggler of an offcanvas, while another is open + EventHandler.one(target, EVENT_HIDDEN$2, () => { + // focus on trigger when it is closed + if ( isVisible(this) ) { + this.focus(); + } + }); // avoid conflict when clicking a toggler of an offcanvas, while another is open - const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); + const allReadyOpen = SelectorEngine.findOne(OPEN_SELECTOR); - if (allReadyOpen && allReadyOpen !== target) { - Offcanvas.getInstance(allReadyOpen).hide(); - } + if ( allReadyOpen && allReadyOpen !== target ) { + Offcanvas.getInstance(allReadyOpen).hide(); + } - const data = Offcanvas.getOrCreateInstance(target); - data.toggle(this); - }); - EventHandler.on(window, EVENT_LOAD_DATA_API$1, () => SelectorEngine.find(OPEN_SELECTOR).forEach(el => Offcanvas.getOrCreateInstance(el).show())); - enableDismissTrigger(Offcanvas); - /** + const data = Offcanvas.getOrCreateInstance(target); + data.toggle(this); + }); + EventHandler.on(window, EVENT_LOAD_DATA_API$1, () => SelectorEngine.find(OPEN_SELECTOR).forEach(el => Offcanvas.getOrCreateInstance(el).show())); + enableDismissTrigger(Offcanvas); + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ */ - defineJQueryPlugin(Offcanvas); + defineJQueryPlugin(Offcanvas); - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): util/sanitizer.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); - const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; - /** + const uriAttributes = new Set(['background', 'cite', 'href', 'itemtype', 'longdesc', 'poster', 'src', 'xlink:href']); + const ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i; + /** * A pattern that recognizes a commonly useful subset of URLs that are safe. * * Shoutout to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts */ - const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i; - /** + const SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i; + /** * A pattern that matches safe data URLs. Only matches image, video and audio types. * * Shoutout to Angular https://github.com/angular/angular/blob/12.2.x/packages/core/src/sanitization/url_sanitizer.ts */ - const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i; + const DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i; - const allowedAttribute = (attribute, allowedAttributeList) => { - const attributeName = attribute.nodeName.toLowerCase(); + const allowedAttribute = (attribute, allowedAttributeList) => { + const attributeName = attribute.nodeName.toLowerCase(); - if (allowedAttributeList.includes(attributeName)) { - if (uriAttributes.has(attributeName)) { - return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue)); - } + if ( allowedAttributeList.includes(attributeName) ) { + if ( uriAttributes.has(attributeName) ) { + return Boolean(SAFE_URL_PATTERN.test(attribute.nodeValue) || DATA_URL_PATTERN.test(attribute.nodeValue)); + } - return true; - } + return true; + } - const regExp = allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp); // Check if a regular expression validates the attribute. + const regExp = allowedAttributeList.filter(attributeRegex => attributeRegex instanceof RegExp); // Check if a regular expression validates the attribute. - for (let i = 0, len = regExp.length; i < len; i++) { - if (regExp[i].test(attributeName)) { - return true; - } - } + for ( let i = 0, len = regExp.length; i < len; i++ ) { + if ( regExp[i].test(attributeName) ) { + return true; + } + } - return false; - }; + return false; + }; - const DefaultAllowlist = { + const DefaultAllowlist = { // Global attributes allowed on any supplied element below. - '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], - a: ['target', 'href', 'title', 'rel'], - area: [], - b: [], - br: [], - col: [], - code: [], - div: [], - em: [], - hr: [], - h1: [], - h2: [], - h3: [], - h4: [], - h5: [], - h6: [], - i: [], - img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], - li: [], - ol: [], - p: [], - pre: [], - s: [], - small: [], - span: [], - sub: [], - sup: [], - strong: [], - u: [], - ul: [] - }; - function sanitizeHtml(unsafeHtml, allowList, sanitizeFn) { - if (!unsafeHtml.length) { - return unsafeHtml; - } + '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN], + a: ['target', 'href', 'title', 'rel'], + area: [], + b: [], + br: [], + col: [], + code: [], + div: [], + em: [], + hr: [], + h1: [], + h2: [], + h3: [], + h4: [], + h5: [], + h6: [], + i: [], + img: ['src', 'srcset', 'alt', 'title', 'width', 'height'], + li: [], + ol: [], + p: [], + pre: [], + s: [], + small: [], + span: [], + sub: [], + sup: [], + strong: [], + u: [], + ul: [], + }; + function sanitizeHtml (unsafeHtml, allowList, sanitizeFn) { + if ( ! unsafeHtml.length ) { + return unsafeHtml; + } - if (sanitizeFn && typeof sanitizeFn === 'function') { - return sanitizeFn(unsafeHtml); - } + if ( sanitizeFn && typeof sanitizeFn === 'function' ) { + return sanitizeFn(unsafeHtml); + } - const domParser = new window.DOMParser(); - const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); - const elements = [].concat(...createdDocument.body.querySelectorAll('*')); + const domParser = new window.DOMParser(); + const createdDocument = domParser.parseFromString(unsafeHtml, 'text/html'); + const elements = [].concat(...createdDocument.body.querySelectorAll('*')); - for (let i = 0, len = elements.length; i < len; i++) { - const element = elements[i]; - const elementName = element.nodeName.toLowerCase(); + for ( let i = 0, len = elements.length; i < len; i++ ) { + const element = elements[i]; + const elementName = element.nodeName.toLowerCase(); - if (!Object.keys(allowList).includes(elementName)) { - element.remove(); - continue; - } + if ( ! Object.keys(allowList).includes(elementName) ) { + element.remove(); + continue; + } - const attributeList = [].concat(...element.attributes); - const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); - attributeList.forEach(attribute => { - if (!allowedAttribute(attribute, allowedAttributes)) { - element.removeAttribute(attribute.nodeName); + const attributeList = [].concat(...element.attributes); + const allowedAttributes = [].concat(allowList['*'] || [], allowList[elementName] || []); + attributeList.forEach(attribute => { + if ( ! allowedAttribute(attribute, allowedAttributes) ) { + element.removeAttribute(attribute.nodeName); + } + }); } - }); - } - return createdDocument.body.innerHTML; - } + return createdDocument.body.innerHTML; + } - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): tooltip.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$4 = 'tooltip'; - const DATA_KEY$4 = 'bs.tooltip'; - const EVENT_KEY$4 = `.${DATA_KEY$4}`; - const CLASS_PREFIX$1 = 'bs-tooltip'; - const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']); - const DefaultType$3 = { - animation: 'boolean', - template: 'string', - title: '(string|element|function)', - trigger: 'string', - delay: '(number|object)', - html: 'boolean', - selector: '(string|boolean)', - placement: '(string|function)', - offset: '(array|string|function)', - container: '(string|element|boolean)', - fallbackPlacements: 'array', - boundary: '(string|element)', - customClass: '(string|function)', - sanitize: 'boolean', - sanitizeFn: '(null|function)', - allowList: 'object', - popperConfig: '(null|object|function)' - }; - const AttachmentMap = { - AUTO: 'auto', - TOP: 'top', - RIGHT: isRTL() ? 'left' : 'right', - BOTTOM: 'bottom', - LEFT: isRTL() ? 'right' : 'left' - }; - const Default$3 = { - animation: true, - template: '', - trigger: 'hover focus', - title: '', - delay: 0, - html: false, - selector: false, - placement: 'top', - offset: [0, 0], - container: false, - fallbackPlacements: ['top', 'right', 'bottom', 'left'], - boundary: 'clippingParents', - customClass: '', - sanitize: true, - sanitizeFn: null, - allowList: DefaultAllowlist, - popperConfig: null - }; - const Event$2 = { - HIDE: `hide${EVENT_KEY$4}`, - HIDDEN: `hidden${EVENT_KEY$4}`, - SHOW: `show${EVENT_KEY$4}`, - SHOWN: `shown${EVENT_KEY$4}`, - INSERTED: `inserted${EVENT_KEY$4}`, - CLICK: `click${EVENT_KEY$4}`, - FOCUSIN: `focusin${EVENT_KEY$4}`, - FOCUSOUT: `focusout${EVENT_KEY$4}`, - MOUSEENTER: `mouseenter${EVENT_KEY$4}`, - MOUSELEAVE: `mouseleave${EVENT_KEY$4}` - }; - const CLASS_NAME_FADE$2 = 'fade'; - const CLASS_NAME_MODAL = 'modal'; - const CLASS_NAME_SHOW$2 = 'show'; - const HOVER_STATE_SHOW = 'show'; - const HOVER_STATE_OUT = 'out'; - const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'; - const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`; - const EVENT_MODAL_HIDE = 'hide.bs.modal'; - const TRIGGER_HOVER = 'hover'; - const TRIGGER_FOCUS = 'focus'; - const TRIGGER_CLICK = 'click'; - const TRIGGER_MANUAL = 'manual'; - /** - * ------------------------------------------------------------------------ - * Class Definition - * ------------------------------------------------------------------------ - */ - - class Tooltip extends BaseComponent { - constructor(element, config) { - if (typeof Popper === 'undefined') { - throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); - } - - super(element); // private - - this._isEnabled = true; - this._timeout = 0; - this._hoverState = ''; - this._activeTrigger = {}; - this._popper = null; // Protected - - this._config = this._getConfig(config); - this.tip = null; - - this._setListeners(); - } // Getters - - - static get Default() { - return Default$3; - } - - static get NAME() { - return NAME$4; - } + const NAME$4 = 'tooltip'; + const DATA_KEY$4 = 'bs.tooltip'; + const EVENT_KEY$4 = `.${DATA_KEY$4}`; + const CLASS_PREFIX$1 = 'bs-tooltip'; + const DISALLOWED_ATTRIBUTES = new Set(['sanitize', 'allowList', 'sanitizeFn']); + const DefaultType$3 = { + animation: 'boolean', + template: 'string', + title: '(string|element|function)', + trigger: 'string', + delay: '(number|object)', + html: 'boolean', + selector: '(string|boolean)', + placement: '(string|function)', + offset: '(array|string|function)', + container: '(string|element|boolean)', + fallbackPlacements: 'array', + boundary: '(string|element)', + customClass: '(string|function)', + sanitize: 'boolean', + sanitizeFn: '(null|function)', + allowList: 'object', + popperConfig: '(null|object|function)', + }; + const AttachmentMap = { + AUTO: 'auto', + TOP: 'top', + RIGHT: isRTL() ? 'left' : 'right', + BOTTOM: 'bottom', + LEFT: isRTL() ? 'right' : 'left', + }; + const Default$3 = { + animation: true, + template: '', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + selector: false, + placement: 'top', + offset: [0, 0], + container: false, + fallbackPlacements: ['top', 'right', 'bottom', 'left'], + boundary: 'clippingParents', + customClass: '', + sanitize: true, + sanitizeFn: null, + allowList: DefaultAllowlist, + popperConfig: null, + }; + const Event$2 = { + HIDE: `hide${EVENT_KEY$4}`, + HIDDEN: `hidden${EVENT_KEY$4}`, + SHOW: `show${EVENT_KEY$4}`, + SHOWN: `shown${EVENT_KEY$4}`, + INSERTED: `inserted${EVENT_KEY$4}`, + CLICK: `click${EVENT_KEY$4}`, + FOCUSIN: `focusin${EVENT_KEY$4}`, + FOCUSOUT: `focusout${EVENT_KEY$4}`, + MOUSEENTER: `mouseenter${EVENT_KEY$4}`, + MOUSELEAVE: `mouseleave${EVENT_KEY$4}`, + }; + const CLASS_NAME_FADE$2 = 'fade'; + const CLASS_NAME_MODAL = 'modal'; + const CLASS_NAME_SHOW$2 = 'show'; + const HOVER_STATE_SHOW = 'show'; + const HOVER_STATE_OUT = 'out'; + const SELECTOR_TOOLTIP_INNER = '.tooltip-inner'; + const SELECTOR_MODAL = `.${CLASS_NAME_MODAL}`; + const EVENT_MODAL_HIDE = 'hide.bs.modal'; + const TRIGGER_HOVER = 'hover'; + const TRIGGER_FOCUS = 'focus'; + const TRIGGER_CLICK = 'click'; + const TRIGGER_MANUAL = 'manual'; + /** + * ------------------------------------------------------------------------ + * Class Definition + * ------------------------------------------------------------------------ + */ - static get Event() { - return Event$2; - } + class Tooltip extends BaseComponent { + constructor (element, config) { + if ( typeof Popper === 'undefined' ) { + throw new TypeError('Bootstrap\'s tooltips require Popper (https://popper.js.org)'); + } - static get DefaultType() { - return DefaultType$3; - } // Public + super(element); // private + this._isEnabled = true; + this._timeout = 0; + this._hoverState = ''; + this._activeTrigger = {}; + this._popper = null; // Protected - enable() { - this._isEnabled = true; - } + this._config = this._getConfig(config); + this.tip = null; - disable() { - this._isEnabled = false; - } + this._setListeners(); + } // Getters - toggleEnabled() { - this._isEnabled = !this._isEnabled; - } + static get Default () { + return Default$3; + } - toggle(event) { - if (!this._isEnabled) { - return; - } + static get NAME () { + return NAME$4; + } - if (event) { - const context = this._initializeOnDelegatedTarget(event); + static get Event () { + return Event$2; + } - context._activeTrigger.click = !context._activeTrigger.click; + static get DefaultType () { + return DefaultType$3; + } // Public - if (context._isWithActiveTrigger()) { - context._enter(null, context); - } else { - context._leave(null, context); + enable () { + this._isEnabled = true; } - } else { - if (this.getTipElement().classList.contains(CLASS_NAME_SHOW$2)) { - this._leave(null, this); - return; + disable () { + this._isEnabled = false; } - this._enter(null, this); - } - } + toggleEnabled () { + this._isEnabled = !this._isEnabled; + } - dispose() { - clearTimeout(this._timeout); - EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + toggle (event) { + if ( ! this._isEnabled ) { + return; + } - if (this.tip) { - this.tip.remove(); - } + if ( event ) { + const context = this._initializeOnDelegatedTarget(event); - this._disposePopper(); + context._activeTrigger.click = !context._activeTrigger.click; - super.dispose(); - } + if ( context._isWithActiveTrigger() ) { + context._enter(null, context); + } else { + context._leave(null, context); + } + } else { + if ( this.getTipElement().classList.contains(CLASS_NAME_SHOW$2) ) { + this._leave(null, this); - show() { - if (this._element.style.display === 'none') { - throw new Error('Please use show on visible elements'); - } + return; + } - if (!(this.isWithContent() && this._isEnabled)) { - return; - } + this._enter(null, this); + } + } - const showEvent = EventHandler.trigger(this._element, this.constructor.Event.SHOW); - const shadowRoot = findShadowRoot(this._element); - const isInTheDom = shadowRoot === null ? this._element.ownerDocument.documentElement.contains(this._element) : shadowRoot.contains(this._element); + dispose () { + clearTimeout(this._timeout); + EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); - if (showEvent.defaultPrevented || !isInTheDom) { - return; - } // A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title` - // This will be removed later in favor of a `setContent` method + if ( this.tip ) { + this.tip.remove(); + } + this._disposePopper(); - if (this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML) { - this._disposePopper(); + super.dispose(); + } - this.tip.remove(); - this.tip = null; - } + show () { + if ( this._element.style.display === 'none' ) { + throw new Error('Please use show on visible elements'); + } - const tip = this.getTipElement(); - const tipId = getUID(this.constructor.NAME); - tip.setAttribute('id', tipId); + if ( ! (this.isWithContent() && this._isEnabled) ) { + return; + } - this._element.setAttribute('aria-describedby', tipId); + const showEvent = EventHandler.trigger(this._element, this.constructor.Event.SHOW); + const shadowRoot = findShadowRoot(this._element); + const isInTheDom = shadowRoot === null ? this._element.ownerDocument.documentElement.contains(this._element) : shadowRoot.contains(this._element); - if (this._config.animation) { - tip.classList.add(CLASS_NAME_FADE$2); - } + if ( showEvent.defaultPrevented || !isInTheDom ) { + return; + } // A trick to recreate a tooltip in case a new title is given by using the NOT documented `data-bs-original-title` + // This will be removed later in favor of a `setContent` method - const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement; + if ( this.constructor.NAME === 'tooltip' && this.tip && this.getTitle() !== this.tip.querySelector(SELECTOR_TOOLTIP_INNER).innerHTML ) { + this._disposePopper(); - const attachment = this._getAttachment(placement); + this.tip.remove(); + this.tip = null; + } - this._addAttachmentClass(attachment); + const tip = this.getTipElement(); + const tipId = getUID(this.constructor.NAME); + tip.setAttribute('id', tipId); - const { - container - } = this._config; - Data.set(tip, this.constructor.DATA_KEY, this); + this._element.setAttribute('aria-describedby', tipId); - if (!this._element.ownerDocument.documentElement.contains(this.tip)) { - container.append(tip); - EventHandler.trigger(this._element, this.constructor.Event.INSERTED); - } + if ( this._config.animation ) { + tip.classList.add(CLASS_NAME_FADE$2); + } - if (this._popper) { - this._popper.update(); - } else { - this._popper = createPopper(this._element, tip, this._getPopperConfig(attachment)); - } + const placement = typeof this._config.placement === 'function' ? this._config.placement.call(this, tip, this._element) : this._config.placement; - tip.classList.add(CLASS_NAME_SHOW$2); + const attachment = this._getAttachment(placement); - const customClass = this._resolvePossibleFunction(this._config.customClass); + this._addAttachmentClass(attachment); - if (customClass) { - tip.classList.add(...customClass.split(' ')); - } // If this is a touch-enabled device we add extra - // empty mouseover listeners to the body's immediate children; - // only needed because of broken event delegation on iOS - // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html + const { + container, + } = this._config; + Data.set(tip, this.constructor.DATA_KEY, this); + if ( ! this._element.ownerDocument.documentElement.contains(this.tip) ) { + container.append(tip); + EventHandler.trigger(this._element, this.constructor.Event.INSERTED); + } - if ('ontouchstart' in document.documentElement) { - [].concat(...document.body.children).forEach(element => { - EventHandler.on(element, 'mouseover', noop); - }); - } + if ( this._popper ) { + this._popper.update(); + } else { + this._popper = createPopper(this._element, tip, this._getPopperConfig(attachment)); + } - const complete = () => { - const prevHoverState = this._hoverState; - this._hoverState = null; - EventHandler.trigger(this._element, this.constructor.Event.SHOWN); + tip.classList.add(CLASS_NAME_SHOW$2); - if (prevHoverState === HOVER_STATE_OUT) { - this._leave(null, this); - } - }; + const customClass = this._resolvePossibleFunction(this._config.customClass); - const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE$2); + if ( customClass ) { + tip.classList.add(...customClass.split(' ')); + } // If this is a touch-enabled device we add extra + // empty mouseover listeners to the body's immediate children; + // only needed because of broken event delegation on iOS + // https://www.quirksmode.org/blog/archives/2014/02/mouse_event_bub.html - this._queueCallback(complete, this.tip, isAnimated); - } + if ( 'ontouchstart' in document.documentElement ) { + [].concat(...document.body.children).forEach(element => { + EventHandler.on(element, 'mouseover', noop); + }); + } - hide() { - if (!this._popper) { - return; - } + const complete = () => { + const prevHoverState = this._hoverState; + this._hoverState = null; + EventHandler.trigger(this._element, this.constructor.Event.SHOWN); - const tip = this.getTipElement(); + if ( prevHoverState === HOVER_STATE_OUT ) { + this._leave(null, this); + } + }; - const complete = () => { - if (this._isWithActiveTrigger()) { - return; - } + const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE$2); - if (this._hoverState !== HOVER_STATE_SHOW) { - tip.remove(); + this._queueCallback(complete, this.tip, isAnimated); } - this._cleanTipClass(); + hide () { + if ( ! this._popper ) { + return; + } - this._element.removeAttribute('aria-describedby'); + const tip = this.getTipElement(); - EventHandler.trigger(this._element, this.constructor.Event.HIDDEN); + const complete = () => { + if ( this._isWithActiveTrigger() ) { + return; + } - this._disposePopper(); - }; + if ( this._hoverState !== HOVER_STATE_SHOW ) { + tip.remove(); + } - const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE); + this._cleanTipClass(); - if (hideEvent.defaultPrevented) { - return; - } + this._element.removeAttribute('aria-describedby'); - tip.classList.remove(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we remove the extra - // empty mouseover listeners we added for iOS support + EventHandler.trigger(this._element, this.constructor.Event.HIDDEN); - if ('ontouchstart' in document.documentElement) { - [].concat(...document.body.children).forEach(element => EventHandler.off(element, 'mouseover', noop)); - } + this._disposePopper(); + }; - this._activeTrigger[TRIGGER_CLICK] = false; - this._activeTrigger[TRIGGER_FOCUS] = false; - this._activeTrigger[TRIGGER_HOVER] = false; - const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE$2); + const hideEvent = EventHandler.trigger(this._element, this.constructor.Event.HIDE); - this._queueCallback(complete, this.tip, isAnimated); + if ( hideEvent.defaultPrevented ) { + return; + } - this._hoverState = ''; - } + tip.classList.remove(CLASS_NAME_SHOW$2); // If this is a touch-enabled device we remove the extra + // empty mouseover listeners we added for iOS support - update() { - if (this._popper !== null) { - this._popper.update(); - } - } // Protected + if ( 'ontouchstart' in document.documentElement ) { + [].concat(...document.body.children).forEach(element => EventHandler.off(element, 'mouseover', noop)); + } + this._activeTrigger[TRIGGER_CLICK] = false; + this._activeTrigger[TRIGGER_FOCUS] = false; + this._activeTrigger[TRIGGER_HOVER] = false; + const isAnimated = this.tip.classList.contains(CLASS_NAME_FADE$2); - isWithContent() { - return Boolean(this.getTitle()); - } + this._queueCallback(complete, this.tip, isAnimated); - getTipElement() { - if (this.tip) { - return this.tip; - } - - const element = document.createElement('div'); - element.innerHTML = this._config.template; - const tip = element.children[0]; - this.setContent(tip); - tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); - this.tip = tip; - return this.tip; - } + this._hoverState = ''; + } - setContent(tip) { - this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER); - } + update () { + if ( this._popper !== null ) { + this._popper.update(); + } + } // Protected - _sanitizeAndSetContent(template, content, selector) { - const templateElement = SelectorEngine.findOne(selector, template); + isWithContent () { + return Boolean(this.getTitle()); + } - if (!content && templateElement) { - templateElement.remove(); - return; - } // we use append for html objects to maintain js events + getTipElement () { + if ( this.tip ) { + return this.tip; + } + const element = document.createElement('div'); + element.innerHTML = this._config.template; + const tip = element.children[0]; + this.setContent(tip); + tip.classList.remove(CLASS_NAME_FADE$2, CLASS_NAME_SHOW$2); + this.tip = tip; + return this.tip; + } - this.setElementContent(templateElement, content); - } + setContent (tip) { + this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TOOLTIP_INNER); + } - setElementContent(element, content) { - if (element === null) { - return; - } + _sanitizeAndSetContent (template, content, selector) { + const templateElement = SelectorEngine.findOne(selector, template); - if (isElement$1(content)) { - content = getElement(content); // content is a DOM node or a jQuery + if ( !content && templateElement ) { + templateElement.remove(); + return; + } // we use append for html objects to maintain js events - if (this._config.html) { - if (content.parentNode !== element) { - element.innerHTML = ''; - element.append(content); - } - } else { - element.textContent = content.textContent; + this.setElementContent(templateElement, content); } - return; - } - - if (this._config.html) { - if (this._config.sanitize) { - content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn); - } + setElementContent (element, content) { + if ( element === null ) { + return; + } - element.innerHTML = content; - } else { - element.textContent = content; - } - } + if ( isElement$1(content) ) { + content = getElement(content); // content is a DOM node or a jQuery - getTitle() { - const title = this._element.getAttribute('data-bs-original-title') || this._config.title; + if ( this._config.html ) { + if ( content.parentNode !== element ) { + element.innerHTML = ''; + element.append(content); + } + } else { + element.textContent = content.textContent; + } - return this._resolvePossibleFunction(title); - } + return; + } - updateAttachment(attachment) { - if (attachment === 'right') { - return 'end'; - } + if ( this._config.html ) { + if ( this._config.sanitize ) { + content = sanitizeHtml(content, this._config.allowList, this._config.sanitizeFn); + } - if (attachment === 'left') { - return 'start'; - } + element.innerHTML = content; + } else { + element.textContent = content; + } + } - return attachment; - } // Private + getTitle () { + const title = this._element.getAttribute('data-bs-original-title') || this._config.title; + return this._resolvePossibleFunction(title); + } - _initializeOnDelegatedTarget(event, context) { - return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); - } + updateAttachment (attachment) { + if ( attachment === 'right' ) { + return 'end'; + } - _getOffset() { - const { - offset - } = this._config; + if ( attachment === 'left' ) { + return 'start'; + } - if (typeof offset === 'string') { - return offset.split(',').map(val => Number.parseInt(val, 10)); - } + return attachment; + } // Private - if (typeof offset === 'function') { - return popperData => offset(popperData, this._element); - } + _initializeOnDelegatedTarget (event, context) { + return context || this.constructor.getOrCreateInstance(event.delegateTarget, this._getDelegateConfig()); + } - return offset; - } + _getOffset () { + const { + offset, + } = this._config; - _resolvePossibleFunction(content) { - return typeof content === 'function' ? content.call(this._element) : content; - } + if ( typeof offset === 'string' ) { + return offset.split(',').map(val => Number.parseInt(val, 10)); + } - _getPopperConfig(attachment) { - const defaultBsPopperConfig = { - placement: attachment, - modifiers: [{ - name: 'flip', - options: { - fallbackPlacements: this._config.fallbackPlacements - } - }, { - name: 'offset', - options: { - offset: this._getOffset() - } - }, { - name: 'preventOverflow', - options: { - boundary: this._config.boundary - } - }, { - name: 'arrow', - options: { - element: `.${this.constructor.NAME}-arrow` - } - }, { - name: 'onChange', - enabled: true, - phase: 'afterWrite', - fn: data => this._handlePopperPlacementChange(data) - }], - onFirstUpdate: data => { - if (data.options.placement !== data.placement) { - this._handlePopperPlacementChange(data); - } - } - }; - return { ...defaultBsPopperConfig, - ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig) - }; - } + if ( typeof offset === 'function' ) { + return popperData => offset(popperData, this._element); + } - _addAttachmentClass(attachment) { - this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`); - } + return offset; + } - _getAttachment(placement) { - return AttachmentMap[placement.toUpperCase()]; - } + _resolvePossibleFunction (content) { + return typeof content === 'function' ? content.call(this._element) : content; + } - _setListeners() { - const triggers = this._config.trigger.split(' '); + _getPopperConfig (attachment) { + const defaultBsPopperConfig = { + placement: attachment, + modifiers: [{ + name: 'flip', + options: { + fallbackPlacements: this._config.fallbackPlacements, + }, + }, { + name: 'offset', + options: { + offset: this._getOffset(), + }, + }, { + name: 'preventOverflow', + options: { + boundary: this._config.boundary, + }, + }, { + name: 'arrow', + options: { + element: `.${this.constructor.NAME}-arrow`, + }, + }, { + name: 'onChange', + enabled: true, + phase: 'afterWrite', + fn: data => this._handlePopperPlacementChange(data), + }], + onFirstUpdate: data => { + if ( data.options.placement !== data.placement ) { + this._handlePopperPlacementChange(data); + } + }, + }; + return { ...defaultBsPopperConfig, + ...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig), + }; + } - triggers.forEach(trigger => { - if (trigger === 'click') { - EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event)); - } else if (trigger !== TRIGGER_MANUAL) { - const eventIn = trigger === TRIGGER_HOVER ? this.constructor.Event.MOUSEENTER : this.constructor.Event.FOCUSIN; - const eventOut = trigger === TRIGGER_HOVER ? this.constructor.Event.MOUSELEAVE : this.constructor.Event.FOCUSOUT; - EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event)); - EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event)); + _addAttachmentClass (attachment) { + this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(attachment)}`); } - }); - this._hideModalHandler = () => { - if (this._element) { - this.hide(); + _getAttachment (placement) { + return AttachmentMap[placement.toUpperCase()]; } - }; - EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + _setListeners () { + const triggers = this._config.trigger.split(' '); + + triggers.forEach(trigger => { + if ( trigger === 'click' ) { + EventHandler.on(this._element, this.constructor.Event.CLICK, this._config.selector, event => this.toggle(event)); + } else if ( trigger !== TRIGGER_MANUAL ) { + const eventIn = trigger === TRIGGER_HOVER ? this.constructor.Event.MOUSEENTER : this.constructor.Event.FOCUSIN; + const eventOut = trigger === TRIGGER_HOVER ? this.constructor.Event.MOUSELEAVE : this.constructor.Event.FOCUSOUT; + EventHandler.on(this._element, eventIn, this._config.selector, event => this._enter(event)); + EventHandler.on(this._element, eventOut, this._config.selector, event => this._leave(event)); + } + }); - if (this._config.selector) { - this._config = { ...this._config, - trigger: 'manual', - selector: '' - }; - } else { - this._fixTitle(); - } - } + this._hideModalHandler = () => { + if ( this._element ) { + this.hide(); + } + }; + + EventHandler.on(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler); + + if ( this._config.selector ) { + this._config = { ...this._config, + trigger: 'manual', + selector: '', + }; + } else { + this._fixTitle(); + } + } - _fixTitle() { - const title = this._element.getAttribute('title'); + _fixTitle () { + const title = this._element.getAttribute('title'); - const originalTitleType = typeof this._element.getAttribute('data-bs-original-title'); + const originalTitleType = typeof this._element.getAttribute('data-bs-original-title'); - if (title || originalTitleType !== 'string') { - this._element.setAttribute('data-bs-original-title', title || ''); + if ( title || originalTitleType !== 'string' ) { + this._element.setAttribute('data-bs-original-title', title || ''); - if (title && !this._element.getAttribute('aria-label') && !this._element.textContent) { - this._element.setAttribute('aria-label', title); - } + if ( title && !this._element.getAttribute('aria-label') && !this._element.textContent ) { + this._element.setAttribute('aria-label', title); + } - this._element.setAttribute('title', ''); - } - } + this._element.setAttribute('title', ''); + } + } - _enter(event, context) { - context = this._initializeOnDelegatedTarget(event, context); + _enter (event, context) { + context = this._initializeOnDelegatedTarget(event, context); - if (event) { - context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; - } + if ( event ) { + context._activeTrigger[event.type === 'focusin' ? TRIGGER_FOCUS : TRIGGER_HOVER] = true; + } - if (context.getTipElement().classList.contains(CLASS_NAME_SHOW$2) || context._hoverState === HOVER_STATE_SHOW) { - context._hoverState = HOVER_STATE_SHOW; - return; - } + if ( context.getTipElement().classList.contains(CLASS_NAME_SHOW$2) || context._hoverState === HOVER_STATE_SHOW ) { + context._hoverState = HOVER_STATE_SHOW; + return; + } - clearTimeout(context._timeout); - context._hoverState = HOVER_STATE_SHOW; + clearTimeout(context._timeout); + context._hoverState = HOVER_STATE_SHOW; - if (!context._config.delay || !context._config.delay.show) { - context.show(); - return; - } + if ( !context._config.delay || !context._config.delay.show ) { + context.show(); + return; + } - context._timeout = setTimeout(() => { - if (context._hoverState === HOVER_STATE_SHOW) { - context.show(); + context._timeout = setTimeout(() => { + if ( context._hoverState === HOVER_STATE_SHOW ) { + context.show(); + } + }, context._config.delay.show); } - }, context._config.delay.show); - } - - _leave(event, context) { - context = this._initializeOnDelegatedTarget(event, context); - if (event) { - context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); - } + _leave (event, context) { + context = this._initializeOnDelegatedTarget(event, context); - if (context._isWithActiveTrigger()) { - return; - } + if ( event ) { + context._activeTrigger[event.type === 'focusout' ? TRIGGER_FOCUS : TRIGGER_HOVER] = context._element.contains(event.relatedTarget); + } - clearTimeout(context._timeout); - context._hoverState = HOVER_STATE_OUT; + if ( context._isWithActiveTrigger() ) { + return; + } - if (!context._config.delay || !context._config.delay.hide) { - context.hide(); - return; - } + clearTimeout(context._timeout); + context._hoverState = HOVER_STATE_OUT; - context._timeout = setTimeout(() => { - if (context._hoverState === HOVER_STATE_OUT) { - context.hide(); - } - }, context._config.delay.hide); - } + if ( !context._config.delay || !context._config.delay.hide ) { + context.hide(); + return; + } - _isWithActiveTrigger() { - for (const trigger in this._activeTrigger) { - if (this._activeTrigger[trigger]) { - return true; + context._timeout = setTimeout(() => { + if ( context._hoverState === HOVER_STATE_OUT ) { + context.hide(); + } + }, context._config.delay.hide); } - } - - return false; - } - _getConfig(config) { - const dataAttributes = Manipulator.getDataAttributes(this._element); - Object.keys(dataAttributes).forEach(dataAttr => { - if (DISALLOWED_ATTRIBUTES.has(dataAttr)) { - delete dataAttributes[dataAttr]; - } - }); - config = { ...this.constructor.Default, - ...dataAttributes, - ...(typeof config === 'object' && config ? config : {}) - }; - config.container = config.container === false ? document.body : getElement(config.container); - - if (typeof config.delay === 'number') { - config.delay = { - show: config.delay, - hide: config.delay - }; - } + _isWithActiveTrigger () { + for ( const trigger in this._activeTrigger ) { + if ( this._activeTrigger[trigger] ) { + return true; + } + } - if (typeof config.title === 'number') { - config.title = config.title.toString(); - } + return false; + } - if (typeof config.content === 'number') { - config.content = config.content.toString(); - } + _getConfig (config) { + const dataAttributes = Manipulator.getDataAttributes(this._element); + Object.keys(dataAttributes).forEach(dataAttr => { + if ( DISALLOWED_ATTRIBUTES.has(dataAttr) ) { + delete dataAttributes[dataAttr]; + } + }); + config = { ...this.constructor.Default, + ...dataAttributes, + ...(typeof config === 'object' && config ? config : {}), + }; + config.container = config.container === false ? document.body : getElement(config.container); + + if ( typeof config.delay === 'number' ) { + config.delay = { + show: config.delay, + hide: config.delay, + }; + } - typeCheckConfig(NAME$4, config, this.constructor.DefaultType); + if ( typeof config.title === 'number' ) { + config.title = config.title.toString(); + } - if (config.sanitize) { - config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn); - } + if ( typeof config.content === 'number' ) { + config.content = config.content.toString(); + } - return config; - } + typeCheckConfig(NAME$4, config, this.constructor.DefaultType); - _getDelegateConfig() { - const config = {}; + if ( config.sanitize ) { + config.template = sanitizeHtml(config.template, config.allowList, config.sanitizeFn); + } - for (const key in this._config) { - if (this.constructor.Default[key] !== this._config[key]) { - config[key] = this._config[key]; + return config; } - } // In the future can be replaced with: - // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) - // `Object.fromEntries(keysWithDifferentValues)` + _getDelegateConfig () { + const config = {}; - return config; - } + for ( const key in this._config ) { + if ( this.constructor.Default[key] !== this._config[key] ) { + config[key] = this._config[key]; + } + } // In the future can be replaced with: + // const keysWithDifferentValues = Object.entries(this._config).filter(entry => this.constructor.Default[entry[0]] !== this._config[entry[0]]) + // `Object.fromEntries(keysWithDifferentValues)` - _cleanTipClass() { - const tip = this.getTipElement(); - const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g'); - const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex); + return config; + } - if (tabClass !== null && tabClass.length > 0) { - tabClass.map(token => token.trim()).forEach(tClass => tip.classList.remove(tClass)); - } - } + _cleanTipClass () { + const tip = this.getTipElement(); + const basicClassPrefixRegex = new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`, 'g'); + const tabClass = tip.getAttribute('class').match(basicClassPrefixRegex); - _getBasicClassPrefix() { - return CLASS_PREFIX$1; - } + if ( tabClass !== null && tabClass.length > 0 ) { + tabClass.map(token => token.trim()).forEach(tClass => tip.classList.remove(tClass)); + } + } - _handlePopperPlacementChange(popperData) { - const { - state - } = popperData; + _getBasicClassPrefix () { + return CLASS_PREFIX$1; + } - if (!state) { - return; - } + _handlePopperPlacementChange (popperData) { + const { + state, + } = popperData; - this.tip = state.elements.popper; + if ( ! state ) { + return; + } - this._cleanTipClass(); + this.tip = state.elements.popper; - this._addAttachmentClass(this._getAttachment(state.placement)); - } + this._cleanTipClass(); - _disposePopper() { - if (this._popper) { - this._popper.destroy(); + this._addAttachmentClass(this._getAttachment(state.placement)); + } - this._popper = null; - } - } // Static + _disposePopper () { + if ( this._popper ) { + this._popper.destroy(); + this._popper = null; + } + } // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Tooltip.getOrCreateInstance(this, config); + static jQueryInterface (config) { + return this.each(function () { + const data = Tooltip.getOrCreateInstance(this, config); - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } + if ( typeof config === 'string' ) { + if ( typeof data[config] === 'undefined' ) { + throw new TypeError(`No method named "${config}"`); + } - data[config](); + data[config](); + } + }); } - }); - } - } - /** + } + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Tooltip to jQuery only if jQuery is present */ + defineJQueryPlugin(Tooltip); - defineJQueryPlugin(Tooltip); - - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): popover.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$3 = 'popover'; - const DATA_KEY$3 = 'bs.popover'; - const EVENT_KEY$3 = `.${DATA_KEY$3}`; - const CLASS_PREFIX = 'bs-popover'; - const Default$2 = { ...Tooltip.Default, - placement: 'right', - offset: [0, 8], - trigger: 'click', - content: '', - template: '' - }; - const DefaultType$2 = { ...Tooltip.DefaultType, - content: '(string|element|function)' - }; - const Event$1 = { - HIDE: `hide${EVENT_KEY$3}`, - HIDDEN: `hidden${EVENT_KEY$3}`, - SHOW: `show${EVENT_KEY$3}`, - SHOWN: `shown${EVENT_KEY$3}`, - INSERTED: `inserted${EVENT_KEY$3}`, - CLICK: `click${EVENT_KEY$3}`, - FOCUSIN: `focusin${EVENT_KEY$3}`, - FOCUSOUT: `focusout${EVENT_KEY$3}`, - MOUSEENTER: `mouseenter${EVENT_KEY$3}`, - MOUSELEAVE: `mouseleave${EVENT_KEY$3}` - }; - const SELECTOR_TITLE = '.popover-header'; - const SELECTOR_CONTENT = '.popover-body'; - /** + const NAME$3 = 'popover'; + const DATA_KEY$3 = 'bs.popover'; + const EVENT_KEY$3 = `.${DATA_KEY$3}`; + const CLASS_PREFIX = 'bs-popover'; + const Default$2 = { ...Tooltip.Default, + placement: 'right', + offset: [0, 8], + trigger: 'click', + content: '', + template: '', + }; + const DefaultType$2 = { ...Tooltip.DefaultType, + content: '(string|element|function)', + }; + const Event$1 = { + HIDE: `hide${EVENT_KEY$3}`, + HIDDEN: `hidden${EVENT_KEY$3}`, + SHOW: `show${EVENT_KEY$3}`, + SHOWN: `shown${EVENT_KEY$3}`, + INSERTED: `inserted${EVENT_KEY$3}`, + CLICK: `click${EVENT_KEY$3}`, + FOCUSIN: `focusin${EVENT_KEY$3}`, + FOCUSOUT: `focusout${EVENT_KEY$3}`, + MOUSEENTER: `mouseenter${EVENT_KEY$3}`, + MOUSELEAVE: `mouseleave${EVENT_KEY$3}`, + }; + const SELECTOR_TITLE = '.popover-header'; + const SELECTOR_CONTENT = '.popover-body'; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class Popover extends Tooltip { + class Popover extends Tooltip { // Getters - static get Default() { - return Default$2; - } - - static get NAME() { - return NAME$3; - } - - static get Event() { - return Event$1; - } - - static get DefaultType() { - return DefaultType$2; - } // Overrides + static get Default () { + return Default$2; + } + static get NAME () { + return NAME$3; + } - isWithContent() { - return this.getTitle() || this._getContent(); - } + static get Event () { + return Event$1; + } - setContent(tip) { - this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TITLE); + static get DefaultType () { + return DefaultType$2; + } // Overrides - this._sanitizeAndSetContent(tip, this._getContent(), SELECTOR_CONTENT); - } // Private + isWithContent () { + return this.getTitle() || this._getContent(); + } + setContent (tip) { + this._sanitizeAndSetContent(tip, this.getTitle(), SELECTOR_TITLE); - _getContent() { - return this._resolvePossibleFunction(this._config.content); - } + this._sanitizeAndSetContent(tip, this._getContent(), SELECTOR_CONTENT); + } // Private - _getBasicClassPrefix() { - return CLASS_PREFIX; - } // Static + _getContent () { + return this._resolvePossibleFunction(this._config.content); + } + _getBasicClassPrefix () { + return CLASS_PREFIX; + } // Static - static jQueryInterface(config) { - return this.each(function () { - const data = Popover.getOrCreateInstance(this, config); + static jQueryInterface (config) { + return this.each(function () { + const data = Popover.getOrCreateInstance(this, config); - if (typeof config === 'string') { - if (typeof data[config] === 'undefined') { - throw new TypeError(`No method named "${config}"`); - } + if ( typeof config === 'string' ) { + if ( typeof data[config] === 'undefined' ) { + throw new TypeError(`No method named "${config}"`); + } - data[config](); + data[config](); + } + }); } - }); - } - } - /** + } + /** * ------------------------------------------------------------------------ * jQuery * ------------------------------------------------------------------------ * add .Popover to jQuery only if jQuery is present */ + defineJQueryPlugin(Popover); - defineJQueryPlugin(Popover); - - /** + /** * -------------------------------------------------------------------------- * Bootstrap (v5.1.3): scrollspy.js * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) * -------------------------------------------------------------------------- */ - /** + /** * ------------------------------------------------------------------------ * Constants * ------------------------------------------------------------------------ */ - const NAME$2 = 'scrollspy'; - const DATA_KEY$2 = 'bs.scrollspy'; - const EVENT_KEY$2 = `.${DATA_KEY$2}`; - const DATA_API_KEY$1 = '.data-api'; - const Default$1 = { - offset: 10, - method: 'auto', - target: '' - }; - const DefaultType$1 = { - offset: 'number', - method: 'string', - target: '(string|element)' - }; - const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`; - const EVENT_SCROLL = `scroll${EVENT_KEY$2}`; - const EVENT_LOAD_DATA_API = `load${EVENT_KEY$2}${DATA_API_KEY$1}`; - const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; - const CLASS_NAME_ACTIVE$1 = 'active'; - const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; - const SELECTOR_NAV_LIST_GROUP$1 = '.nav, .list-group'; - const SELECTOR_NAV_LINKS = '.nav-link'; - const SELECTOR_NAV_ITEMS = '.nav-item'; - const SELECTOR_LIST_ITEMS = '.list-group-item'; - const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}`; - const SELECTOR_DROPDOWN$1 = '.dropdown'; - const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle'; - const METHOD_OFFSET = 'offset'; - const METHOD_POSITION = 'position'; - /** + const NAME$2 = 'scrollspy'; + const DATA_KEY$2 = 'bs.scrollspy'; + const EVENT_KEY$2 = `.${DATA_KEY$2}`; + const DATA_API_KEY$1 = '.data-api'; + const Default$1 = { + offset: 10, + method: 'auto', + target: '', + }; + const DefaultType$1 = { + offset: 'number', + method: 'string', + target: '(string|element)', + }; + const EVENT_ACTIVATE = `activate${EVENT_KEY$2}`; + const EVENT_SCROLL = `scroll${EVENT_KEY$2}`; + const EVENT_LOAD_DATA_API = `load${EVENT_KEY$2}${DATA_API_KEY$1}`; + const CLASS_NAME_DROPDOWN_ITEM = 'dropdown-item'; + const CLASS_NAME_ACTIVE$1 = 'active'; + const SELECTOR_DATA_SPY = '[data-bs-spy="scroll"]'; + const SELECTOR_NAV_LIST_GROUP$1 = '.nav, .list-group'; + const SELECTOR_NAV_LINKS = '.nav-link'; + const SELECTOR_NAV_ITEMS = '.nav-item'; + const SELECTOR_LIST_ITEMS = '.list-group-item'; + const SELECTOR_LINK_ITEMS = `${SELECTOR_NAV_LINKS}, ${SELECTOR_LIST_ITEMS}, .${CLASS_NAME_DROPDOWN_ITEM}`; + const SELECTOR_DROPDOWN$1 = '.dropdown'; + const SELECTOR_DROPDOWN_TOGGLE$1 = '.dropdown-toggle'; + const METHOD_OFFSET = 'offset'; + const METHOD_POSITION = 'position'; + /** * ------------------------------------------------------------------------ * Class Definition * ------------------------------------------------------------------------ */ - class ScrollSpy extends BaseComponent { - constructor(element, config) { - super(element); - this._scrollElement = this._element.tagName === 'BODY' ? window : this._element; - this._config = this._getConfig(config); - this._offsets = []; - this._targets = []; - this._activeTarget = null; - this._scrollHeight = 0; - EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process()); - this.refresh(); - - this._process(); - } // Getters - - - static get Default() { - return Default$1; - } - - static get NAME() { - return NAME$2; - } // Public - + class ScrollSpy extends BaseComponent { + constructor (element, config) { + super(element); + this._scrollElement = this._element.tagName === 'BODY' ? window : this._element; + this._config = this._getConfig(config); + this._offsets = []; + this._targets = []; + this._activeTarget = null; + this._scrollHeight = 0; + EventHandler.on(this._scrollElement, EVENT_SCROLL, () => this._process()); + this.refresh(); + + this._process(); + } // Getters + + static get Default () { + return Default$1; + } - refresh() { - const autoMethod = this._scrollElement === this._scrollElement.window ? METHOD_OFFSET : METHOD_POSITION; - const offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; - const offsetBase = offsetMethod === METHOD_POSITION ? this._getScrollTop() : 0; - this._offsets = []; - this._targets = []; - this._scrollHeight = this._getScrollHeight(); - const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target); - targets.map(element => { - const targetSelector = getSelectorFromElement(element); - const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null; + static get NAME () { + return NAME$2; + } // Public + + refresh () { + const autoMethod = this._scrollElement === this._scrollElement.window ? METHOD_OFFSET : METHOD_POSITION; + const offsetMethod = this._config.method === 'auto' ? autoMethod : this._config.method; + const offsetBase = offsetMethod === METHOD_POSITION ? this._getScrollTop() : 0; + this._offsets = []; + this._targets = []; + this._scrollHeight = this._getScrollHeight(); + const targets = SelectorEngine.find(SELECTOR_LINK_ITEMS, this._config.target); + targets.map(element => { + const targetSelector = getSelectorFromElement(element); + const target = targetSelector ? SelectorEngine.findOne(targetSelector) : null; + + if ( target ) { + const targetBCR = target.getBoundingClientRect(); + + if ( targetBCR.width || targetBCR.height ) { + return [Manipulator[offsetMethod](target).top + offsetBase, targetSelector]; + } + } + + return null; + }).filter(item => item).sort((a, b) => a[0] - b[0]).forEach(item => { + this._offsets.push(item[0]); + + this._targets.push(item[1]); + }); + } - if (target) { - const targetBCR = target.getBoundingClientRect(); + dispose () { + EventHandler.off(this._scrollElement, EVENT_KEY$2); + super.dispose(); + } // Private + + _getConfig (config) { + config = { ...Default$1, + ...Manipulator.getDataAttributes(this._element), + ...(typeof config === 'object' && config ? config : {}), + }; + config.target = getElement(config.target) || document.documentElement; + typeCheckConfig(NAME$2, config, DefaultType$1); + return config; + } - if (targetBCR.width || targetBCR.height) { - return [Manipulator[offsetMethod](target).top + offsetBase, targetSelector]; - } + _getScrollTop () { + return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; } - return null; - }).filter(item => item).sort((a, b) => a[0] - b[0]).forEach(item => { - this._offsets.push(item[0]); + _getScrollHeight () { + return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); + } - this._targets.push(item[1]); - }); - } + _getOffsetHeight () { + return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height; + } - dispose() { - EventHandler.off(this._scrollElement, EVENT_KEY$2); - super.dispose(); - } // Private + _process () { + const scrollTop = this._getScrollTop() + this._config.offset; + const scrollHeight = this._getScrollHeight(); - _getConfig(config) { - config = { ...Default$1, - ...Manipulator.getDataAttributes(this._element), - ...(typeof config === 'object' && config ? config : {}) - }; - config.target = getElement(config.target) || document.documentElement; - typeCheckConfig(NAME$2, config, DefaultType$1); - return config; - } + const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); - _getScrollTop() { - return this._scrollElement === window ? this._scrollElement.pageYOffset : this._scrollElement.scrollTop; - } + if ( this._scrollHeight !== scrollHeight ) { + this.refresh(); + } - _getScrollHeight() { - return this._scrollElement.scrollHeight || Math.max(document.body.scrollHeight, document.documentElement.scrollHeight); - } + if ( scrollTop >= maxScroll ) { + const target = this._targets[this._targets.length - 1]; - _getOffsetHeight() { - return this._scrollElement === window ? window.innerHeight : this._scrollElement.getBoundingClientRect().height; - } + if ( this._activeTarget !== target ) { + this._activate(target); + } - _process() { - const scrollTop = this._getScrollTop() + this._config.offset; + return; + } - const scrollHeight = this._getScrollHeight(); + if ( this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0 ) { + this._activeTarget = null; - const maxScroll = this._config.offset + scrollHeight - this._getOffsetHeight(); + this._clear(); - if (this._scrollHeight !== scrollHeight) { - this.refresh(); - } + return; + } - if (scrollTop >= maxScroll) { - const target = this._targets[this._targets.length - 1]; + for ( let i = this._offsets.length; i--; ) { + const isActiveTarget = this._activeTarget !== this._targets[i] && scrollTop >= this._offsets[i] && (typeof this._offsets[i + 1] === 'undefined' || scrollTop < this._offsets[i + 1]); - if (this._activeTarget !== target) { - this._activate(target); + if ( isActiveTarget ) { + this._activate(this._targets[i]); + } + } } - return; - } + _activate (target) { + this._activeTarget = target; - if (this._activeTarget && scrollTop < this._offsets[0] && this._offsets[0] > 0) { - this._activeTarget = null; + this._clear(); - this._clear(); + const queries = SELECTOR_LINK_ITEMS.split(',').map(selector => `${selector}[data-bs-target="${target}"],${selector}[href="${target}"]`); + const link = SelectorEngine.findOne(queries.join(','), this._config.target); + link.classList.add(CLASS_NAME_ACTIVE$1); - return; - } + if ( link.classList.contains(CLASS_NAME_DROPDOWN_ITEM) ) { + SelectorEngine.findOne(SELECTOR_DROPDOWN_TOGGLE$1, link.closest(SELECTOR_DROPDOWN$1)).classList.add(CLASS_NAME_ACTIVE$1); + } else { + SelectorEngine.parents(link, SELECTOR_NAV_LIST_GROUP$1).forEach(listGroup => { + // Set triggered links parents as active + // With both
    and